233.82K
Category: programmingprogramming

NET Development

1.

.NET Development
ЗАНЯТИЕ 13

2.

Рассматриваемые вопросы
Вложенные типы
Перечисляемые типы и перечислители
Итератор и операторы yield
Ковариантность и контравариантность

3.

Вложенные типы
Класс или структура (а в C# 8 – и интерфейс) может
содержать описание другого пользовательского типа –
класса, структуры, интерфейса, перечисления, делегата.
Вне обрамляющего типа на вложенный тип ссылаются
так: имя-обрамляющего-типа.имя-вложенного-типа
*) Модификатор видимости по умолчанию для
вложенного типа – это private.

4.

Пример вложенного класса
public class Outer
{
// ссылаемся на вложенный класс
private Inner _inner = new Inner();
public class Inner
{
public int Prop { get; set; }
}
}

5.

Работа с вложенным классом
// важно: у объекта o есть только поле _inner
var o = new Outer();
// важно: у объекта i есть только свойство Prop
var i = new Outer.Inner();
i.Prop = 5;

6.

Доступ к private-элементам
У вложенных типов есть особенность – они имеют
свободный доступ к private-элементам обрамляющих
типов. Но такой доступ надо правильно организовать.
Обычно для этого экземпляр обрамляющего типа
помещают в поле экземпляра вложенного типа (часто
это делают в конструкторе).

7.

Доступ к private-элементам – пример
public class Outer
{
private int field;
public override string ToString() => field.ToString();
public class Inner
{
private readonly Outer _outer;
public Inner(Outer outer) => _outer = outer;
public void Mutator(int x) => _outer.field = x;
}
}

8.

Доступ к private-элементам – пример
var outer = new Outer();
var inner = new Outer.Inner(outer);
inner.Mutator(5);
Console.WriteLine(outer);
// 5

9.

«Невидимый» вложенный тип
public class Outer
{
private readonly int field = 3;
public IComparable<int> Get() => new Inner(this);
private class Inner : IComparable<int>
{
private readonly Outer _outer;
public Inner(Outer outer) => _outer = outer;
public int CompareTo(int x) => _outer.field - x;
}
}

10.

Шаблон «Итератор» (Iterator)
Итератор – это один из шаблонов проектирования.
Итератор обеспечивает последовательный доступ ко
всем элементам некой коллекции, не раскрывая при
этом деталей внутренней реализации коллекции.
Также при помощи итераторов можно сделать обход
разных коллекций единообразным.

11.

Перечисляемые типы и перечислители
Определения (для .NET):
• Перечисляемый тип (enumerable type) – это тип с
экземплярным методом GetEnumerator(),
возвращающим перечислитель.
• Перечислитель (enumerator) – объект со свойством
Current (текущий элемент набора) и методом
MoveNext() для движения к следующему элементу.

12.

Семантика работы цикла foreach
int[] data = {1, 2, 4, 8};
int[] data = {1, 2, 4, 8};
foreach (int item in data)
{
Console.Write(item);
}
var rator = data.GetEnumerator();
while (rator.MoveNext())
{
int item = (int) rator.Current;
Console.Write(item);
}

13.

Как сделать тип перечисляемым
Вариант 1. Реализовать в типе интерфейс IEnumerable, а
в типе перечислителя – интерфейс IEnumerator (из
пространства имён System.Collections).
Вариант 2. Реализовать интерфейсы IEnumerable<T> и
IEnumerator<T> (System.Collections.Generic).
Вариант 3. Использовать соглашение об именах для
элементов перечисляемого типа и перечислителя.
Вариант 4 (C# 9). Создать для типа метод расширения
GetEnumerator(), возвращающий перечислитель.

14.

Интерфейсы IEnumerable и IEnumerator
public interface IEnumerable
{
IEnumerator GetEnumerator();
}
public interface IEnumerator
{
object Current { get; }
bool MoveNext();
void Reset();
}

15.

Интерфейсы IEnumerable и IEnumerator
Свойство Current для универсальности имеет тип object.
Метод MoveNext() возвращает true, если перемещение
по набору возможно. Предполагается, что MoveNext()
нужно вызвать и для получения первого элемента, то
есть начальная позиция – «перед первым элементом».
Метод Reset() сбрасывает позицию в наборе в
начальное состояние (foreach не использует этот метод).

16.

Интерфейсы IEnumerable и IEnumerator
Демонстрация кода 01: перечисляемый тип (класс) с
реализацией интерфейсов IEnumerable и IEnumerator.

17.

IEnumerable<T> и IEnumerator<T>
Это обобщённые версии интерфейсов IEnumerable и
IEnumerator, которые наследуются от своих
необобщённых аналогов.
Причина появления: хотим, чтобы свойство Current
было типизировано.

18.

IEnumerable<T> и IEnumerator<T>
public interface IEnumerable<T>: IEnumerable
{
IEnumerator<T> GetEnumerator();
}
public interface IEnumerator<T>: IDisposable, IEnumerator
{
T Current { get; }
}

19.

IEnumerable<T> и IEnumerator<T>
Демонстрация кода 02: класс с реализацией
интерфейсов IEnumerable<T> и IEnumerator<T>.

20.

Перечисляемый тип без интерфейсов
Интересный факт: тип может быть перечисляемым, даже
не реализуя стандартные интерфейсы.
Для перебора в foreach типу достаточно иметь метод
без параметров с именем GetEnumerator(). Этот метод
может возвращать произвольный объект, содержащий
свойство Current и метод bool MoveNext().

21.

Перечисляемый тип без интерфейсов
Демонстрация кода 03: использование соглашения об
именах для перечисляемого типа и перечислителя.

22.

Метод расширения GetEnumerator()
Чтобы превратить некий тип в перечисляемый, в C# 9
для этого типа можно создать метод расширения с
именем GetEnumerator() и одним this-параметром.
Метод может возвращать или объект, реализующий
IEnumerator/IEnumerator<T>, или объект, следующий
соглашению об именах для перечислителя.

23.

Метод расширения GetEnumerator()
Демонстрация кода 04: конверсия типа в
перечисляемый при помощи метода расширения.

24.

Способы реализации перечислителя
Вариант 1. Использовать пользовательский класс.
Вариант 2. Взять перечислитель у готовой коллекции:
public IEnumerator GetEnumerator()
{
return _items.GetEnumerator(); // у массива
}
Вариант 3. Использовать итератор (в терминах .NET) –
блок кода с операторами yield.

25.

Итератор и операторы yield
Итератор порождает последовательность значений и
может использоваться в методе, у которого тип
возвращаемого значения IEnumerator<T> или
IEnumerable<T> (или их необобщённые версии).
Оператор yield return выражение выдаёт следующее
значение последовательности, а оператор yield break
прекращает генерацию последовательности.

26.

Пример итератора
public class Helper
{
public static IEnumerator<int> GetNumbers(bool short)
{
yield return 1;
Console.Write("Second number: ");
yield return 3;
if (short) yield break;
yield return 5;
Console.WriteLine("Stop");
}
}

27.

Работа с итератором
IEnumerator<int> it = Helper.GetNumbers(false);
while (it.MoveNext())
{
Console.WriteLine(it.Current);
}
//
//
//
//
//
будет напечатано:
1
Second number: 3
5
Stop

28.

Отложенные вычисления
Итераторы позволяют реализовать концепцию
отложенных вычислений (deferred evaluations).
Каждое выполнение оператора yield return может
восприниматься как выход из метода и возврат значения.
Но состояние метода, его локальные переменные и
позиция yield return запоминаются, чтобы быть
восстановленными при следующем вызове (и мы
попадаем в ту точку, откуда вышли прошлый раз).

29.

Пример «бесконечного» итератора
public class Helper
{
public static IEnumerable<int> GetNumbers()
{
int i = 0;
while (true)
{
yield return i++;
}
}
}

30.

Работа с «бесконечным» итератором
// этот код напечатает три числа (и не зациклится!)
foreach (var number in Helper.GetNumbers())
{
Console.WriteLine(number);
if (number == 2)
{
break;
}
}

31.

Итератор – технические детали
Встретив код итератора, компилятор выполняет
трансляцию этого кода в работу конечного автомата.
Этот автомат запоминает контекст выполнения
операторов yield и выполняет определённый фрагмент
кода в зависимости от номера состояния (при этом сам
номер состояния может измениться).

32.

Итератор и операторы yield
Демонстрация кода 05: различные варианты
использования оператора yield:
• пример реализации перечислителя;
• генератор последовательности (отложенные
вычисления);
• детали внутренней трансляции оператора yield.

33.

Частичный порядок ссылочных типов
Введём на множестве ссылочных типов отношение
частичного порядка.
Считаем, что
English     Русский Rules