Итераторы

Реализация

Реализовать итеративный доступ к данным можно двумя способами. Первый, достаточно очевидный,  посредством реализации интерфейса IEnumerable. Все стандартные классы коллекций FCL реализуют данный интерфейс.

В том случае, когда данные не хранятся, а вычисляются, или просто нужно перечислить фиксированный набор значений, скажем, поля объекта, можно воспользоваться операторами yield return и yield break.

Рассмотрим простой пример, перечисление полей объекта.

public class Editors
{
  private string _name1;
  private string _name2;
  private string _name3;

  public IEnumerable<string> Names
  {
    get 
    {
	yield return _name1;
	yield return _name2;
	yield return _name3;
	}
    }
}

При использовании любой из этих констркуций, компилятором генерируется анонимный класс, помеченный атрибутом [CompilerGenerated] и реализующий интерфейс IEnumerable. Вся логика перечисления переносится в данный класс, и заменяется кодом возвращающим экземпляр данного класса.

public IEnumerable<string> get_Names()
{
    <get_Names>d__0 d__ = new <get_Names>d__0(-2);
    d__.<>4__this = this;
    return d__;
}

[CompilerGenerated]
private sealed class <get_Names>d__0 : IEnumerable<string>, IEnumerable, \
	IEnumerator<string>, IEnumerator, IDisposable
{
 ...
};

Единственное предназначение конструкции yield break, на мой взгляд, создание пустых коллеций, поскольку при налиичии хотя бы одного yield return, компилятором генерируется корректный класс-перечисление. Другими словами, если в коде есть хотя бы один оператор yield return, yield break для выхода можно не использовать. В приведеном ниже примере два класса, один возвращает итератор чётных чисел, второй — пустую коллекцию.

namespace Lib 
{ 
  public interface IDataFilter 
  { 
    IEnumerable<int> Filter(IEnumerable<int> source); 
  } 
	
  public class EvenNumberFilter : IDataFilter 
  { 
    public EvenNumberFilter() 
    { 
    } 
		
    public IEnumerable<int> Filter(IEnumerable<int> source) 
    { 
      if (source != null) 
      {
        foreach(int i in source) 
        { 
          if ((i & 1) != 1) 
            yield return i; 
        } 
      }
     /* второй вариант, с использованием yiled break;
        if (source == null) 
            yield break;

        foreach(int i in source) 
        { 
            if ((i & 1) != 1) 
                yield return i; 
        } 
     */
    } 
  }
	
  public class NullFilter : IDataFilter 
  { 
    public IEnumerable<int> Filter(IEnumerable<int> source) 
    { 
       yield break; 
    } 
  } 
}

Remoting

Анонимные классы, сгенерированные компилятором не наследуются от MarshalByRefObject и не сериализуемы. Передача такого итератора в другой AppDomain приведет к генерации исключения. Для того, что бы объект-итератор, созданный в одном AppDomain (сервере), можно было передать для использования в другом AppDomain (клиенту), его нужно либо реализовать явно, в виде класса наследника MarshalByRefObject либо указать атрибут [Serializable].

Классы стандартных коллекций, входящих в FCL, помечены атрибутом [Serializable], поэтому их использование при remoting-взаимодействии безопасно (в том случае, когда элементы коллекций наследники MarshalByRefObject или сериализуемы).