Обещаю, что в этот раз будет короткая статья (относительно). Все вы знаете языковую конструкцию foreach в C#, не так ли? Но подумайте дважды прежде чем сказать как именно работает следующий код:
Уже знаете ответ? Позвольте мне разочаровать вас: если у вас только один ответ, то вы ошибаетесь. Нет единственного ответа на поставленный вопрос, поскольку вы должны знать больше о типе переменной src чтобы принять окончательное решение насчет того, как вышеприведенный код работает…
Очевидно, вы, должно быть, скажете, что объект должен реализовывать IEnumerable или IEnumerable<T> и, может быть, вы даже упомянете, что в первом случае компилятор приводит тип за вас когда получает значение «x», вызывая свойство IEnumerator.Current. Другими словами, вы преобразуете код в нечто вроде этого:
Достойная попытка, но не совсем верная. Прежде всего, переменная x объявлена во внешней зоне видимости (что причиняет некоторые неприятности, если говорить о замыканиях, но сейчас у нас совсем другая тема...). Во-вторых, перечислитель может реализовывать IDisposable, и в этом случае конструкция foreach обеспечивает корректное высвобождение а ля “using”:
Это уже более разумно, но мы пропустили другой тип источника, с которым может работать foreach: это любой объект, до тех пор, пока он предоставляет шаблон перечисления GetEnumerator в тандеме с MoveNext и Current. Вот для примера объект, который просто замечательно работает с конструкцией foreach.
Как это используется, показано ниже:
Ok, гибко, не правда ли? В самом деле, можно сказать, что в конструкции foreach утиная типизация: имеет значение не номинальный тип (т.е. когда Source явно объявлен как IEnumerable и SourceEnumerator как IEnumerator), а лишь структура объекта, которая и определяет «совместимость» с конструкцией foreach.
Но кто сказал, что foreach над коллекцией сразу начинает думать о LINQ? Допустим, класс Source используется вот так:
Выглядит как прекрасный кандидат для LINQ, особенно, если бы мы начали добавлять все больше и больше логики в наш «запрос». Ничего удивительно в таком заключении, но в реальности, к сожалению, это падает и не компилируется:
Почему? Потому что в LINQ статическая типизация (update: в этом месте автор просит прочитать комментарии к его статье и соглашается с тем, что более точным было бы в данном случае говорить о LINQ to Objects), так что LINQ ожидает, что я сошлюсь на номинальную имплементацию перечислителя: на что-то, что явно определено как IEnumerable, а не на что-то, что «случайно» оказалось похожим на IEnumerable. Вопрос дня: как преобразовать существующий структурный перечислитель в номинальный так, чтобы его можно было использовать с LINQ? Конечно, мы можем написать специальный код для объекта Source, который создаст необходимый итератор из Source:
Но быть может вы в такой ситуации, когда вокруг целое изобилие таких структурных перечислителей (например, некоторые библиотеки автоматизации Office предоставляют GetEnumerator в типах вроде Range, в то время как тип Range не реализует интерфейс IEnumerable, следовательно, он не подходит для использования с LINQ), так что вы хотите обобщить вышеприведенное решение. По сути нам нужна возможность надстроить над любым объектом итератор с утиной типизацией и это подходящая задача для расщиряющего метода и ключевого слова dynamic из C# 4.0:
Вопрос к читателю: почему мы не можем просто написать цикл foreach над «объектом, который приведен к dynamic»? Подсказка: как тогда вы реализуете перевод конструкции foreach в dynamic-объекте?
Да, вы нагромоздите необходимый список методов на System.Object, так что будьте осторожны с использованием этого или же просто используйте вызов старого плоского метода, чтобы «перевести» структурное в номинальное. Обратите внимание каким легким выглядит динамически типизированный код в C# 4.0. С большим количеством приведений типов это выглядит примерно так:
И теперь мы можем написать так:
Динамический клей – почему бы нет? Фактически, даже объекты из других языков (как Ruby или Python), которые следуют парадигме утиной типизации теперь работают с LINQ, и для существующих совместимых объектов вызов оператора безвреден (но расточителен). Ох, и обратите внимание, что вы можете также иметь IEnumerable в «динамических» объектах, если вы имеете дело с объектами из динамических языков…
Можете ли вы реализовать метод AsDuckEnumerable в C# 3.0? Конечно, если вы ограничите себя методами основанными на рефлексии (оставлено в качестве упражнения для читателя).
Наслаждайтесь!
- foreach (int x in src)
- {
- // Do something with x.
- }
* This source code was highlighted with Source Code Highlighter.
Уже знаете ответ? Позвольте мне разочаровать вас: если у вас только один ответ, то вы ошибаетесь. Нет единственного ответа на поставленный вопрос, поскольку вы должны знать больше о типе переменной src чтобы принять окончательное решение насчет того, как вышеприведенный код работает…
Очевидно, вы, должно быть, скажете, что объект должен реализовывать IEnumerable или IEnumerable<T> и, может быть, вы даже упомянете, что в первом случае компилятор приводит тип за вас когда получает значение «x», вызывая свойство IEnumerator.Current. Другими словами, вы преобразуете код в нечто вроде этого:
- var e = src.GetEnumerator();
- while (e.MoveNext())
- {
- var x = (int)e.Current; // without the cast if src was an IEnumerable<T>
- // Do something with x.
- }
* This source code was highlighted with Source Code Highlighter.
Достойная попытка, но не совсем верная. Прежде всего, переменная x объявлена во внешней зоне видимости (что причиняет некоторые неприятности, если говорить о замыканиях, но сейчас у нас совсем другая тема...). Во-вторых, перечислитель может реализовывать IDisposable, и в этом случае конструкция foreach обеспечивает корректное высвобождение а ля “using”:
- {
- int x;
-
- using (var e = src.GetEnumerator())
- {
- while (e.MoveNext())
- {
- x = (int)e.Current; // without the cast if src was an IEnumerable<T>
- // Do something with x.
- }
- }
- }
* This source code was highlighted with Source Code Highlighter.
Это уже более разумно, но мы пропустили другой тип источника, с которым может работать foreach: это любой объект, до тех пор, пока он предоставляет шаблон перечисления GetEnumerator в тандеме с MoveNext и Current. Вот для примера объект, который просто замечательно работает с конструкцией foreach.
- class Source
- {
- public SourceEnumerator GetEnumerator()
- {
- return new SourceEnumerator();
- }
- }
-
- class SourceEnumerator
- {
- private Random rand = new Random();
-
- public bool MoveNext()
- {
- return rand.Next(100) != 0;
- }
-
- public int Current
- {
- get
- {
- return rand.Next(100);
- }
- }
- }
* This source code was highlighted with Source Code Highlighter.
Как это используется, показано ниже:
- foreach (int x in new Source())
- Console.WriteLine(x);
* This source code was highlighted with Source Code Highlighter.
Ok, гибко, не правда ли? В самом деле, можно сказать, что в конструкции foreach утиная типизация: имеет значение не номинальный тип (т.е. когда Source явно объявлен как IEnumerable и SourceEnumerator как IEnumerator), а лишь структура объекта, которая и определяет «совместимость» с конструкцией foreach.
Но кто сказал, что foreach над коллекцией сразу начинает думать о LINQ? Допустим, класс Source используется вот так:
- List<int> res = new List<int>();
- foreach (int x in new Source())
- if (x % 2 == 0)
- res.Add(x);
* This source code was highlighted with Source Code Highlighter.
Выглядит как прекрасный кандидат для LINQ, особенно, если бы мы начали добавлять все больше и больше логики в наш «запрос». Ничего удивительно в таком заключении, но в реальности, к сожалению, это падает и не компилируется:
Почему? Потому что в LINQ статическая типизация (update: в этом месте автор просит прочитать комментарии к его статье и соглашается с тем, что более точным было бы в данном случае говорить о LINQ to Objects), так что LINQ ожидает, что я сошлюсь на номинальную имплементацию перечислителя: на что-то, что явно определено как IEnumerable, а не на что-то, что «случайно» оказалось похожим на IEnumerable. Вопрос дня: как преобразовать существующий структурный перечислитель в номинальный так, чтобы его можно было использовать с LINQ? Конечно, мы можем написать специальный код для объекта Source, который создаст необходимый итератор из Source:
- static void Main()
- {
- var res = from x in IterateOver(new Source())
- where x % 2 == 0
- select x;
-
- foreach (var x in res)
- Console.WriteLine(x);
- }
-
- static IEnumerable<int> IterateOver(Source s)
- {
- foreach (int i in s)
- yield return i;
- }
* This source code was highlighted with Source Code Highlighter.
Но быть может вы в такой ситуации, когда вокруг целое изобилие таких структурных перечислителей (например, некоторые библиотеки автоматизации Office предоставляют GetEnumerator в типах вроде Range, в то время как тип Range не реализует интерфейс IEnumerable, следовательно, он не подходит для использования с LINQ), так что вы хотите обобщить вышеприведенное решение. По сути нам нужна возможность надстроить над любым объектом итератор с утиной типизацией и это подходящая задача для расщиряющего метода и ключевого слова dynamic из C# 4.0:
- static class DuckEnumerable
- {
- public static IEnumerable<T> AsDuckEnumerable<T>(this object source)
- {
- dynamic src = source;
-
- var e = src.GetEnumerator();
- try
- {
- while (e.MoveNext())
- yield return e.Current;
- }
- finally
- {
- var d = e as IDisposable;
- if (d != null)
- {
- d.Dispose();
- }
- }
- }
- }
* This source code was highlighted with Source Code Highlighter.
Вопрос к читателю: почему мы не можем просто написать цикл foreach над «объектом, который приведен к dynamic»? Подсказка: как тогда вы реализуете перевод конструкции foreach в dynamic-объекте?
Да, вы нагромоздите необходимый список методов на System.Object, так что будьте осторожны с использованием этого или же просто используйте вызов старого плоского метода, чтобы «перевести» структурное в номинальное. Обратите внимание каким легким выглядит динамически типизированный код в C# 4.0. С большим количеством приведений типов это выглядит примерно так:
- static class DuckEnumerable
- {
- public static IEnumerable<T> AsDuckEnumerable<T>(this object source)
- {
- dynamic src = (dynamic)source;
-
- dynamic e = src.GetEnumerator();
- try
- {
- while ((bool)e.MoveNext())
- yield return (T)e.Current;
- }
- finally
- {
- var d = e as IDisposable;
- if (d != null)
- {
- d.Dispose();
- }
- }
- }
- }
* This source code was highlighted with Source Code Highlighter.
И теперь мы можем написать так:
- var res = from x in new Source().AsDuckEnumerable<int>()
- where x % 2 == 0
- select x;
-
- foreach (var x in res)
- Console.WriteLine(x);
* This source code was highlighted with Source Code Highlighter.
Динамический клей – почему бы нет? Фактически, даже объекты из других языков (как Ruby или Python), которые следуют парадигме утиной типизации теперь работают с LINQ, и для существующих совместимых объектов вызов оператора безвреден (но расточителен). Ох, и обратите внимание, что вы можете также иметь IEnumerable в «динамических» объектах, если вы имеете дело с объектами из динамических языков…
Можете ли вы реализовать метод AsDuckEnumerable в C# 3.0? Конечно, если вы ограничите себя методами основанными на рефлексии (оставлено в качестве упражнения для читателя).
Наслаждайтесь!