Similar presentations:
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.
Семантика работы цикла foreachint[] 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 и IEnumeratorpublic 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.
Частичный порядок ссылочных типовВведём на множестве ссылочных типов отношение
частичного порядка.
Считаем, что