Возврат перечислителей посредством yield return

С помощью оператора yieldможно также делать и более сложные вещи, например, возвращать перечислитель из yield return.

В игре “крестики-нолики” имеется девять полей, где игроки-соперники расставля­ют крестики или нолики. Эти ходы имитируются классом GameMoves.Метод Cross() и Circle() - это блоки итератора для создания типов итераторов. Переменные crossи circleустанавливаются в Cross() и Circle() внутри конструктора класса GameMoves.Для установки этих полей методы не вызываются, но устанавливаются в типы итераторов, определенные в блоках итераторов. Внутри блока итератора Cross() информация о ходах выводится на консоль и номер хода увеличивается. Если номер хода больше 8, итератор завершается с помощью yieldbreak;в противном случае на каждой итерации возвраща­ется объект перечислителя circle.Блок итератора Circle() очень похож на блок итера­тора Cross(), только он возвращает на каждой итерации объект перечислителя cross.

public class GameMoves

{

private IEnumerator cross;

private IEnumerator circle;

public GameMoves()

{

cross = Cross();

circle = Circle();

}

private int move = 0;

const int MaxMoves = 9;

public IEnumerator Cross()

{

while (true)

{

Console.WriteLine("Крестик, ход {0}", move);

if (++move >= MaxMoves) yield break;

yield return circle;

}

}

public IEnumerator Circle()

{

while (true)

{

Console.WriteLine("Нолик, ход {0}”, move);

if(++move >= MaxMoves) yield break;

yield return cross;

}

}

}

В клиентской программе использовать класс GameMovesможно так, как показано ниже. Первый ход выполняется установкой перечислителя в тип перечислителя, возвращенный game.Cross(). В цикле whileвызывается enumerator.MoveNext.При первом его вызо­ве вызывается метод Cross(), возвращающий другой перечислитель с помощью операто­ра yield.Возвращенное значение можно получить через свойство Currentи затем оно устанавливается в переменную enumeratorдля следующего шага цикла:

GameMoves game = new GameMoves();

IEnumerator enumerator = game.Cross();

while (enumerator.MoveNext())

{

enumerator = enumerator.Current as IEnumerator;

}

Вывод этой программы показывает все ходы игроков до самого последнего:

Крестик, ход 0

Нолик, ход 1

Крестик, ход 2

Нолик, ход 3

Крестик, ход 4

Нолик, ход 5

Крестик, ход 6

Нолик, ход 7

Крестик, ход 8

Кортежи

Массивы комбинируют объекты одного типа, а кортежи (tuple) могут комбинировать объекты различных типов. Понятие кортежей происходит из языков функционального программирования, таких как F#, где они часто используются. С появлением .NET 4 корте­жи стали доступны в .NET Framework для всех языков .NET.

В .NET 4 определены восемь обобщенных классов Tupleи один статический класс Tuple,который служит фабрикой кортежей. Существуют различные обобщенные классы Tupleдля поддержки различного количества элементов; например, Tuple<T1>содержит один элемент, Tuple<T1,Т2>- два элемента и т.д.

Метод Divide() демонстрирует возврат кортежа с двумя членами - Tuple<int,int>.Параметры обобщенного класса определяют типы членов, которые в данном случае оба це­лочисленные. Кортеж создан статическим методом Create() статического класса Tuple.Обобщенные параметры метода Create() определяют тип создаваемого экземпляра кор­тежа. Вновь созданный кортеж инициализируется переменными resultи reminderдля возврата результата деления:

public static Tuple<int,int> Divide (int dividend, int divisor)

{

int result = dividend/divisor;

int reminder = dividend%divisor;

return Tuple.Create<int, int>(result, reminder);

}

В следующем коде показан вызов метода Divide(). Элементы кортежа могут быть дос­тупны через свойства Item1и Item2.

var result = Divide(5,2);

Console.WriteLine("результат деления: (0), остаток: {1}",

result.Item1, result.Item2);

В случае если имеется более восьми элементов, которые нужно включить в кортеж, можно использовать определение класса Tupleс восемью параметрами. Последний пара­метр называется TRest,в котором должен передаваться сам кортеж. Таким образом, есть возможность создавать кортежи с любым количеством параметров.

Для демонстрации этой функциональности напишем следующий код:

public class Tuple<T1, Т2, Т3, Т4, Т5, Т6, Т7, TRest>

Здесь последний параметр шаблона - сам тип кортежа, так что можно создать кортеж с любым числом элементов:

var tuple = Tuple.Create<string,string,string,int,int,int,double,

Tuple<int,int> ("Stephanie", "Alina", "Nagel", 2009, 6, 2, 1.37,

Tuple.Create<int,int>(52, 3490));

Структурное сравнение

Как массивы, так и кортежи реализуют интерфейс IStructuralEquatableи IStructuralComparable.Эти интерфейсы появились в .NET 4 и позволяют сравнивать не только ссылки, но и содержимое. Интерфейс реализован явно, поэтому при его использо­вании необходимо выполнять приведения массивов и кортежей. IStructuralEquatableслужит для определения того, имеют ли два кортежа или массива одинаковое содержимое, a IStructuralComparableприменяется для сортировки кортежей и массивов.

В следующем примере, демонстрирующем использование IStructuralEquatable,создан класс Person,который реализует интерфейс IEquatable.Этот интерфейс опреде­ляет строго типизированный метод Equals(), в котором сравниваются значения свойств FirstNameи LastName:

public class Person: IEquatable<Person>

{

public int Id {get; private set; }

public string FirstName {get; set;}

public string LastName {get; set;}

public override string ToString()

{

return String.Format("{0}, {1} {2}", Id, FirstName, LastName);

}

public override bool Equals(object obj)

{

if(obj == null) throw new ArgumentNullException("obj");

return Equals(obj as Person);

}

public override int GetHashCode()

{

return Id.GetHashCode();

}

public bool Equals(Person other)

{

if (other == null) throw new ArgumentNullException("other");

return this.Id==other.Id && this.FirstName==other.FirstName &&

this.LastName == other.LastName;

}

}

Ниже создаются два массива элементов Person.Оба они содержат один и тот же объ­ект Personс переменной по имени janetи два разных объекта Personс одинаковым со­держимым. Операция сравнения != возвращает true,потому что на самом деле это два разных массива, на которые ссылаются две переменные по имени persons1и persons2.Поскольку метод Equals() с одним параметром не переопределяется классом Array,то же самое случается и с операцией == при сравнении ссылок - они не совпадают.

var janet = new Person {FirstName = "Janet”, LastName = "Jackson"};

Person [] persons1 = { new Person

{

FirstName = "Michael",

LastName = "Jackson"

},

janet

};

Person[] persons2 = { new Person

{

FirstName = "Michael",

LastName = "Jackson"

},

janet

};

if (persons1 != persons2)

Console.WriteLine("разные ссылки");

Вызывая метод Equals(), определенный в IStructuralEquatableкак принимаю­щий первый параметр типа objectи второй типа IEqualityComparer,можно опре­делить, как именно должно выполняться сравнение, передавая объект, реализующий IEqualityComparer<T>.Реализация IEqualityComparerпо умолчанию предоставля­ется классом EqualityComparer<T>.В ней производится проверка, реализует ли тип интерфейс IEquatable,и вызывается IEquatable.Equals(). Если тип не реализует IEquatable,то для выполнения сравнения вызывается метод Equals() базового класса Object.

Класс Personреализует IEquatable<Person>,где содержимое объектов сравнивается, и оказывается, что массивы действительно включают одинаковое содержимое:

if ((persons1 as IStructuralEquatable).Equals(persons2,

EqualityComparer<Person>.Default))

{

Console.WriteLine("одинаковое содержимое");

}

Теперь будет показано, как то же самое можно сделать с применением кортежей. Ниже создаются два экземпляра кортежей с одинаковым содержимым. Разумеется, поскольку ссыл­ки t1и t2указывают на два разный объекта, операция сравнения != возвращает true:

var t1 = Tuple.Create<int, string>(l, "Stephanie");

var t2 = Tuple.Create<int, string>(l, "Stephanie");

if (t1 != t2) Console.WriteLine("не одинаковое содержимое");

Класс Tuple<>предоставляет два метода Equals(): один, переопределяющий реа­лизацию базового класса Object,с objectв качестве параметра, а второй опреде­лен интерфейсом IStructuralEqualityComparer,с двумя параметрами - objectи IEqualityComparer.Как показано, другой кортеж может быть передан в первый метод. Чтобы получить ObjectEqualityComparer<object>для сравнения, этот метод использу­ет EqualityComparer<object>.Default.Таким образом, каждый элемент в кортеже срав­нивается за счет вызова метода Object.Equals(). Если для каждого элемента возвращает­ся true,конечным результатом метода Equals() также будет true,что мы и видим здесь с одинаковыми значениями intи string:

if (t1.Equals(t2)) Console.WriteLine("одинаковое содержимое");

Можно также создать специальный интерфейс IEqualityComparer,как показано ниже на примере класса TupleComparer.В этом классе реализованы два метода - Equals() и GetHashCode() - интерфейса IEqualityComparer.

class TupleComparer: IEqualityComparer

{

public new bool Equals(object x, object y)

{

return x.EquaLs(y);

}

public int GetHashCode(object obj)

{

return obj.GetHashCode();

}

}

Реализация метода Equals() интерфейса IEqualityComparer требует мо­дификатора new или неявной реализации интерфейса, потому что базовый класс Object также определяет статический метод Equals() с двумя параметрами.

TupleComparerиспользуется при передаче нового экземпляра методу Equals()класса Tuple<T1,Т2>.Метод Equals() класса Tupleвызывает метод Equals() класса TupleComparerдля каждого сравниваемого элемента. Поэтому с классом Tuple<T1, Т2>класс TupleComparerвызывается два раза для проверки эквивалентности всех элементов:

if (t1.Equals(t2, new TupleComparer()))

Console.WriteLine("равны после проверки с помощью TupleComparer");

Итоги

В этом разделе мы познакомились с нотацией C# для создания и использования простых, многомерных и зубчатых массивов. “За кулисами” механизма массивов C# применяется класс Array, и таким образом имеется возможность обращаться к свойствам и методам этого класса через переменные массива.

Было показано, как сортировать элементы массива с использованием интерфейсов IComparable и IComparer.

Вы познакомились с использованием и созданием перечислителей, с интерфейсами IEnumerable и IEnumerator, а также оператором yield. Кроме того, мы получили пред­ставление о кортежах - новом средстве .NET 4.

Наши рекомендации