В ходе обсуждения предыдущего поста возник вот такой вопрос: «Не совсем представляю, как это по произвольному классу, который реализует IEnumerable (т.е. просто должен уметь вернуть instance, реализующий IEnumerator), оно само сгенерирует реализацию IEnumerator». “Оно” – это в данном случае компилятор C# :)
Начну издалека. Для перебора элементов коллекций в C# есть удобный оператор цикла – foreach, который в общем виде выглядит вот так:
foreach (Type varName in enumerableObject)
{
...
}
Например:
IEnumerable<string> _names = new List<string> { "a", "b" };
foreach (string name in _names)
{
Console.WriteLine(name);
}
На самом деле компилятор развернет этот код вот так:
IEnumerable<string> _names = new List<string> { "a", "b" };
IEnumerator<string> ne = _names.GetEnumerator();
while (ne.MoveNext())
{
string name = ne.Current;
Console.WriteLine(name);
}
Получается, что foreach выполняет два действия:
- вызывает у переданной ему коллекции метод GetEnumerator()
- используя полученный объект IEnumerator, пробегается по всем элементам коллекции
Давайте посмотрим, что представляет собой IEnumerator:
public interface IEnumerator
{
bool MoveNext();
object Current { get; }
void Reset();
}
public interface IEnumerator<T> : IDisposable, IEnumerator
{
T Current { get; }
}
C# реализует специальную конструкцию – итератор, которая позволяет не создавать IEnumerator руками, каждый раз, когда вам это понадобится. Давайте посмотрим как это выглядит в коде. Класс OpenFileControl (неважно откуда) получает список имен файлов и реализует метод GetStreams(), который возвращает IEnumerable<Stream> для этого списка. Это почти боевой пример. Реальный OpenFileControl получает и имена, и стримы для файлов с WCF сервиса.
public class OpenFileControl
{
private IEnumerable<string> _files = new List<string>();
public IEnumerable<Stream> GetStreams()
{
foreach (string file in _files)
{
yield return File.Open(file, FileMode.Open);
}
}
}
Теперь воспользуемся Reflector’ом чтобы посмотреть, что сгенерил для этого примера компилятор:
public class OpenFileControl
{
private IEnumerable<string> _files;
public IEnumerable<Stream> GetStreams();
// Nested Types
[CompilerGenerated]
private sealed class <GetStreams>d__0 : IEnumerable<Stream>, IEnumerable, IEnumerator<Stream>, IEnumerator, IDisposable
{
// Fields
private int <>1__state;
private Stream <>2__current;
public OpenFileControl <>4__this;
public IEnumerator<string> <>7__wrap2;
private int <>l__initialThreadId;
public string <file>5__1;
// Methods
[DebuggerHidden]
public <GetStreams>d__0(int <>1__state);
private void <>m__Finally3();
private bool MoveNext();
[DebuggerHidden]
IEnumerator<Stream> IEnumerable<Stream>.GetEnumerator();
[DebuggerHidden]
IEnumerator IEnumerable.GetEnumerator();
[DebuggerHidden]
void IEnumerator.Reset();
void IDisposable.Dispose();
// Properties
Stream IEnumerator<Stream>.Current { [DebuggerHidden] get; }
object IEnumerator.Current { [DebuggerHidden] get; }
}
}
Имена у сгенерированных объектов, конечно, не особо читабельные, но разобраться все-таки можно. Вы видите, что в OpenFileControl добавился приватный класс, реализующий одновременно и IEnumerable<Stream>, и IEnumerator<Stream>. При этом наш метод GetStreams() изменился и теперь он просто возвращает экземпляр сгенерированного класса:
public IEnumerable<Stream> GetStreams()
{
return new <GetStreams>d__0(-2) { <>4__this = this };
}
Логика по перебору элементов коллекции, как и ожидалось, переехала в метод MoveNext() сгенерированного энумератора.
private bool MoveNext()
{
try
{
switch (this.<>1__state)
{
case 0:
this.<>1__state = -1;
this.<>7__wrap2 = this.<>4__this._files.GetEnumerator();
this.<>1__state = 1;
while (this.<>7__wrap2.MoveNext())
{
this.<file>5__1 = this.<>7__wrap2.Current;
this.<>2__current = File.Open(this.<file>5__1, FileMode.Open);
this.<>1__state = 2;
return true;
Label_0078:
this.<>1__state = 1;
}
this.<>m__Finally3();
break;
case 2:
goto Label_0078;
}
return false;
}
fault
{
this.System.IDisposable.Dispose();
}
}
Использовать OpenFileControl можно примерно так:
OpenFileControl ofc = new OpenFileControl();
foreach (Stream s in ofc.GetStreams())
{
using (s)
{
byte[] buf = new byte[10];
s.Read(buf, 0, 10);
// ...
}
}
Обратите внимание на то, что GetStreams() не открывает все файлы одновременно. На каждой итерации цикла открыт только текущий файл.
No comments:
Post a Comment