Необязательные параметры конструктора экземпляров

Вид this(...) инициализатора конструктора обычно используется в сочетании с перегрузкой для реализации необязательных параметров конструктора экземпляров. В этом примере

class Text
{
public Text(): this(0, 0, null) {}

public Text(int x, int y): this(x, y, null) {}

public Text(int x, int y, string s) {
// Actual constructor implementation
}
}

В этом примере первые два конструктора экземпляров только предоставляют значения по умолчанию для отсутствующих аргументов. Оба используют инициализатор конструктора this(...) для вызова третьего конструктора экземпляров, который действительно выполняет работу по инициализации нового экземпляра. Это действие соответствует действию необязательных параметров конструктора:

Text t1 = new Text(); // Same as Text(0, 0, null)
Text t2 = new Text(5, 10); // Same as Text(5, 10, null)
Text t3 = new Text(5, 20, "Hello");

Статические конструкторы

Статический конструктор – это член, реализующий действия, необходимые для инициализации закрытого типа класса. Статические конструкторы объявляются с помощью объявлений статического конструктора:

static-constructor-declaration:
attributesopt static-constructor-modifiers identifier ( ) static-constructor-body

static-constructor-modifiers:
externopt static
static externopt

static-constructor-body:
block
;

Объявление статического конструктора может включать набор атрибутов (§17) и модификатор extern (§10.6.7).

Идентификатор в объявлении статического конструктора должен указывать имя класса, в котором объявлен статический конструктор. Если указано любое другое имя, происходит ошибка времени компиляции.

Если в объявление статического конструктора включен модификатор extern, статический конструктор называется внешним статическим конструктором. Так как объявление внешнего статического конструктора не предоставляет фактическую реализацию, его тело статического конструктора состоит из точки с запятой. Для всех других объявлений статического конструктора тело статического конструктора состоит из блока, в котором указаны операторы, которые необходимо выполнить, чтобы инициализировать класс. Это в точности соответствует телу метода статического метода с типом возвращаемого значения void (§10.6.10).

Статические конструкторы не наследуются и их нельзя вызвать непосредственно.

Статические конструкторы для закрытого типа класса выполняются не более одного раза в данной области приложения. Выполнение статического конструктора запускается первым из следующих событий, происходящих внутри области приложения:

· создан экземпляр типа класса;

· возникла ссылка на любой статический член типа класса.

Если класс содержит метод Main (§3.1), в котором начинается исполнение, статический конструктор для этого класса выполняется до вызова метода Main.

Для инициализации нового закрытого типа класса сначала создается новый набор статических полей (§10.5.1) для этого особого типа класса. Каждое из статических полей инициализируется своим значением по умолчанию (§5.2). Затем выполняются инициализаторы этих статических полей (§10.4.5.1). Наконец выполняется статический конструктор.

Пример:

using System;

class Test
{
static void Main() {
A.F();
B.F();
}
}

class A
{
static A() {
Console.WriteLine("Init A");
}
public static void F() {
Console.WriteLine("A.F");
}
}

class B
{
static B() {
Console.WriteLine("Init B");
}
public static void F() {
Console.WriteLine("B.F");
}
}

Выходом этого примера должно быть:

Init A
A.F
Init B
B.F

так как выполнение статического конструктора класса A запускается по обращению к A.F, а выполнение статического конструктора класса B запускается обращением к B.F.

Можно создать циклические зависимости, позволяющие наблюдать статические поля с инициализаторами переменных в их состоянии значений по умолчанию.

Пример:

using System;

class A
{
public static int X;

static A() {
X = B.Y + 1;
}
}

class B
{
public static int Y = A.X + 1;

static B() {}

static void Main() {
Console.WriteLine("X = {0}, Y = {1}", A.X, B.Y);
}
}

производятся следующие выходные данные

X = 1, Y = 2

Для выполнения метода Main система сначала выполняет инициализатор для B.Y, до статического конструктора класса B. Инициализатор Y вызывает запуск статического конструктора класса A, так как имеется ссылка на значение A.X. Статический конструктор A в свою очередь продолжает вычислять значение X и при этом вычислении выбирает значение по умолчанию для Y, которое равно нулю. A.XA.X таким образом инициализируется значением 1. Процесс выполнения для класса A инициализаторов статических полей и статического конструктора затем завершается, возвращаясь к вычислению начального значения Y, результат которого становится равным 2.

Так как статический конструктор выполняется ровно один раз для каждого закрытого сформированного типа класса, он является удобным местом для принудительных проверок во время выполнения параметров-типов, которые не удается проверить с помощью ограничений во время компиляции (§10.1.5). Например, следующий тип использует статический конструктор для принудительной установки того, что аргументом типа является перечисляемый тип:

class Gen<T> where T: struct
{
static Gen() {
if (!typeof(T).IsEnum) {
throw new ArgumentException("T must be an enum");
}
}
}

Деструкторы

Деструктор является членом, реализующим действия, необходимые для уничтожения экземпляра класса. Деструктор объявляется с помощью объявления деструктора:

destructor-declaration:
attributesopt externopt ~ identifier ( ) destructor-body

destructor-body:
block
;

Объявление деструктора может включать набор атрибутов (§17).

Идентификатор в деклараторе деструктора должен указывать имя класса, в котором объявлен деструктор. Если указано любое другое имя, происходит ошибка времени компиляции.

Если в объявление деструктора включен модификатор extern, деструктор называется внешним деструктором. Поскольку объявление внешнего деструктора не предоставляет фактическую реализацию, его тело деструктора состоит из точки с запятой. Во всех других деструкторах тело деструктора состоит из блока, в котором указаны операторы для уничтожения экземпляра класса. Тело деструктора в точности соответствует телу метода метода экземпляра с типом возвращаемого значения void (§10.6.10).

Деструкторы не наследуются. Таким образом, класс не имеет других деструкторов, кроме того, который может быть объявлен в этом классе.

Так как деструктор должен не иметь параметров, его нельзя перегрузить, поэтому класс может иметь не более одного деструктора.

Деструкторы вызываются автоматически, их нельзя вызвать явно. Экземпляр становится пригодным для уничтожения, когда более невозможно использование этого экземпляра любым кодом. Выполнение деструктора для экземпляра может произойти в любое время после того, как экземпляр становится пригодным для уничтожения. Когда экземпляр уничтожен, вызываются деструкторы в цепочке наследования этого экземпляра по порядку, от старшего производного к младшему. Деструктор может выполняться в любом потоке. Дальнейшее обсуждение правил, определяющих, когда и как выполняется деструктор, см. в §3.9.

Выполнение примера

using System;

class A
{
~A() {
Console.WriteLine("A's destructor");
}
}

class B: A
{
~B() {
Console.WriteLine("B's destructor");
}
}

class Test
{
static void Main() {
B b = new B();
b = null;
GC.Collect();
GC.WaitForPendingFinalizers();
}
}

даст на выходе:

B’s destructor
A’s destructor

так как деструкторы в цепочке наследования вызываются по порядку, от самого старшего производного до самого младшего.

Деструкторы реализуются переопределением виртуального метода Finalize для System.Object. Программам C# запрещено переопределять этот метод или вызывать его (или его переопределения) непосредственно. Например, программа

class A
{
override protected void Finalize() {} // error

public void F() {
this.Finalize(); // error
}
}

содержит две ошибки.

Поведение компилятора такое, как будто этот метод и его переопределения вовсе не существуют. Таким образом, эта программа:

class A
{
void Finalize() {} // permitted
}

является допустимой, а показанный метод скрывает метод Finalize объекта System.Object.

Обсуждение поведения, когда исключение вызывается из деструктора, см. в §16.3.

Итераторы

Член функции (§7.5), реализованный с помощью блока итератора(§8.2), называется итератором.

Блок итератора можно использовать как тело члена функции, пока тип возвращаемого значения соответствующего члена функции является одним из интерфейсов перечислителя (§10.14.1) или одним из перечислимых интерфейсов (§10.14.2). Он может встречаться как тело метода, тело оператора или тело метода доступа, тогда как события, конструкторы экземпляров, статические конструкторы и деструкторы не могут быть реализованы как итераторы.

Если член функции реализован с помощью блока итератора, указание любого из параметров ref или out в списке формальных параметров члена функции является ошибкой времени компиляции.

Интерфейсы перечислителя

Интерфейсы перечислителя – это неуниверсальный интерфейс System.Collections.IEnumerator и все экземпляры универсального интерфейса System.Collections.Generic.IEnumerator<T>. Эти интерфейсы в этой главе для краткости обозначаем как IEnumerator и IEnumerator<T> соответственно.

Перечислимые интерфейсы

Перечислимые интерфейсы – это неуниверсальный интерфейс System.Collections.IEnumerable и все экземпляры универсального интерфейса System.Collections.Generic.IEnumerable<T>. Эти интерфейсы в этой главе для краткости обозначаем как IEnumerable и IEnumerable<T> соответственно.

Тип yield

Итератор создает последовательность значений одного и того же типа. Этот тип называется типом yield итератора.

· Типом выдачи итератора, который возвращает интерфейс IEnumerator или IEnumerable, является object.

· Типом выдачи итератора, который возвращает интерфейс IEnumerator<T> или IEnumerable<T>, является T.

Объекты перечислителя

Если член функции, возвращающий тип интерфейса перечислителя, реализован с помощью блока итератора, при вызове члена функции не происходит немедленное выполнение кода в блоке итератора. Вместо этого создается и возвращается объект перечислителя. Этот объект инкапсулирует код, указанный в блоке итератора, а выполнение кода в блоке итератора происходит тогда, когда вызывается метод MoveNext объекта перечислителя. Объект перечислителя имеет следующие характеристики:

· Он реализует IEnumerator и IEnumerator<T>, где T – это тип yield итератора.

· Он реализует System.IDisposable.

· Он инициализируется копией значений аргумента (при их наличии) и значением экземпляра, переданным члену функции.

· У него есть четыре потенциальных состояния: before, running, suspended и after, а также начальное состояние before.

Обычно объект перечислителя является экземпляром созданного компилятором класса перечислителя, который инкапсулирует код в блоке итератора и реализует интерфейсы перечислителя, но возможны и другие методы реализации. Если класс перечислителя создан компилятором, этот класс будет вложен, прямо или косвенно, в класс, содержащий член функции, у него будет частная доступность и имя, зарезервированное для использования компилятором (§2.4.2).

Объект перечислителя может реализовать больше интерфейсов, чем указано выше.

В следующих разделах описано точное поведение членов MoveNext, Current и Dispose реализаций интерфейсов IEnumerable и IEnumerable<T>, предоставляемых объектом перечислителя.

Обратите внимание, что объекты перечислителя не поддерживают метод IEnumerator.Reset. Вызов этого метода приводит к исключению System.NotSupportedException.

Метод MoveNext

Метод MoveNext объекта перечислителя инкапсулирует код блока итератора. При вызове метода MoveNext выполняется код в блоке итератора и устанавливается соответствующее свойство Current объекта перечислителя. Точное действие, выполняемое MoveNext, зависит от состояния объекта перечислителя при вызове MoveNext:

· Если объект перечислителя находится в состоянии before, то при вызове MoveNext:

o состояние меняется на running;

o инициализируются параметры (включая this) блока итератора значениями аргументов и значением экземпляра, сохраненными при инициализации объекта перечислителя;

o выполняется блок итератора от начала и до тех пор, пока выполнение не будет прервано (как описано ниже).

· Если состояние объекта перечислителя running, результат вызова MoveNext не определен.

· Если состояние объекта перечислителя suspended, при вызове MoveNext:

o состояние меняется на running;

o восстанавливаются значения всех локальных переменных и параметров (включая this) к значениям, сохраненным при последней приостановке выполнения блока итератора. Обратите внимание, что содержимое любых объектов, на которые ссылаются эти переменные, могло измениться со времени предыдущего вызова MoveNext;

o возобновляется выполнение блока итератора с оператора, непосредственно следующего за оператором yield return, вызвавшим приостановку выполнения, и продолжается до тех пор, пока выполнение не будет прервано (как описано ниже).

· Если состояние объекта перечислителя after, при вызове MoveNext возвращается false.

Когда MoveNext выполняет блок итератора, выполнение может быть прервано четырьмя способами: оператором yield return, оператором yield break, по концу блока итератора и инициированным исключением, распространенным из блока итератора.

· При обнаружении оператора yield return (§8.14):

o выражение, заданное в операторе, вычисляется, неявно преобразуется к типу yield и присваивается свойству Current объекта перечислителя;

o выполнение тела итератора приостанавливается. Значения всех локальных переменных и параметров (включая this) сохраняются, как и место этого оператора yield return. Если оператор yield return находится внутри одного или нескольких блоков try, соответствующие им блоки finally не выполняются в это время.

o состояние объекта перечислителя меняется на suspended;

o метод MoveNext возвращает true вызвавшему его, указывая этим, что итерация успешно продвинулась к следующему значению.

· При обнаружении оператора yield break (§8.14):

o Если оператор yield break находится внутри одного или нескольких блоков try, соответствующие им блоки finally выполняются;

o состояние объекта перечислителя меняется на after;

o метод MoveNext возвращает false вызвавшему его, указывая этим, что итерация выполнена.

· Если встретился конец тела итератора:

o состояние объекта перечислителя меняется на after;

o метод MoveNext возвращает false вызвавшему его, указывая этим, что итерация выполнена.

· Если инициируется исключение и распространяется из блока итератора:

o соответствующие блоки finally в теле итератора будут выполняться распространением исключения;

o состояние объекта перечислителя меняется на after;

o распространение исключения продолжается до вызвавшего метод MoveNext.

Свойство Current

На свойство Current объекта перечислителя воздействуют операторы yield return в блоке итератора.

Если объект перечислителя находится в состоянии suspended, значением Current является значение, установленное предыдущим вызовом MoveNext. Если объект перечислителя находится в состоянии before, running или after, результат обращения к Current не определен.

Для итератора с типом выдачи, отличным от object, результат обращения к Current посредством реализации IEnumerable объекта перечислителя соответствует обращению к Current посредством реализации IEnumerator<T> объекта перечислителя и приведением результата к object.

Метод Dispose

Метод Dispose используется для очистки итерации приведением объекта перечислителя в состояние after.

· Если состояние объекта перечислителя before, вызов Dispose меняет это состояние на after.

· Если состояние объекта перечислителя running, результат вызова Dispose не определен.

· Если состояние объекта перечислителя suspended, при вызове Dispose:

o состояние меняется на running;

o выполняются все блоки finally, как если бы последний выполненный оператор yield return был оператором yield break. Если это приводит к инициированию и распространению из тела итератора исключения, состояние объекта перечислителя устанавливается в after, а исключение распространяется до объекта, вызвавшего метод Dispose;

o состояние меняется на after.

· Если состояние объекта перечислителя after, вызов Dispose не оказывает влияние.

Перечислимые объекты

Если член функции, возвращающий перечислимый тип интерфейса, реализован с помощью блока итератора, при вызове члена функции не происходит немедленное выполнение кода в блоке итератора. Вместо этого создается и возвращается перечислимый объект. Метод GetEnumerator перечислимого объекта возвращает объект перечислителя, который инкапсулирует код, указанный в блоке итератора, а выполнение кода в блоке итератора происходит при вызове метода MoveNext объекта перечислителя. Перечислимый объект имеет следующие характеристики:

· Он реализует IEnumerable и IEnumerable<T>, где T – это тип yield итератора.

· Он инициализируется копией значений аргумента (при их наличии) и значением экземпляра, переданным члену функции.

Обычно перечислимый объект является экземпляром созданного компилятором перечислимого класса, который инкапсулирует код в блоке итератора и реализует перечислимые интерфейсы, но возможны и другие методы реализации. Если перечислимый класс создан компилятором, этот класс будет вложен, прямо или косвенно, в класс, содержащий член функции, у него будет частная доступность и имя, зарезервированное для использования компилятором (§2.4.2).

Перечислимый объект может реализовать больше интерфейсов, чем указано выше. В частности, перечислимый объект может также реализовать IEnumerator и IEnumerator<T>, позволяя ему выступать в роли как перечислимого, так и перечислителя. При таком типе реализации при первом вызове метода GetEnumerator перечислимого объекта возвращается сам перечислимый объект. При последующих вызовах GetEnumerator для перечислимого объекта, при их наличии, будет возвращена копия перечислимого объекта. Таким образом, каждый возвращенный перечислитель имеет собственное состояние, и изменения в одном перечислителе не влияют на другой.

Метод GetEnumerator

Перечислимый объект предоставляет реализацию методов GetEnumerator интерфейсов IEnumerable и IEnumerable<T>. Два метода GetEnumerator совместно используют общую реализацию, которая получает и возвращает доступный объект перечислителя. Объект перечислителя инициализируется значениями аргументов и значением экземпляра, сохраненными при инициализации перечислимого объекта, но в других отношениях объект перечислителя функционирует так, как описано в §10.14.4.

Пример реализации

В этом разделе описана возможная реализация итераторов при использования стандартных конструкций C#. В основе реализации, описанной в этом разделе, лежат принципы, используемые в компиляторе Microsoft C#, однако она никоим образом не является обязательной или единственной возможной.

Следующий класс Stack<T> реализует метод GetEnumerator с помощью итератора. Итератор перечисляет элементы стека в нисходящем порядке.

using System;
using System.Collections;
using System.Collections.Generic;

class Stack<T>: IEnumerable<T>
{
T[] items;
int count;

public void Push(T item) {
if (items == null) {
items = new T[4];
}
else if (items.Length == count) {
T[] newItems = new T[count * 2];
Array.Copy(items, 0, newItems, 0, count);
items = newItems;
}
items[count++] = item;
}

public T Pop() {
T result = items[--count];
items[count] = default(T);
return result;
}

public IEnumerator<T> GetEnumerator() {
for (int i = count - 1; i >= 0; --i) yield return items[i];
}
}

Метод GetEnumerator можно транслировать в экземпляр класса перечислителя, созданного компилятором и инкапсулирующего код в блоке итератора, как показано в следующем примере.

class Stack<T>: IEnumerable<T>
{
...

public IEnumerator<T> GetEnumerator() {
return new __Enumerator1(this);
}

class __Enumerator1: IEnumerator<T>, IEnumerator
{
int __state;
T __current;
Stack<T> __this;
int i;

public __Enumerator1(Stack<T> __this) {
this.__this = __this;
}

public T Current {
get { return __current; }
}

object IEnumerator.Current {
get { return __current; }
}

public bool MoveNext() {
switch (__state) {
case 1: goto __state1;
case 2: goto __state2;
}
i = __this.count - 1;
__loop:
if (i < 0) goto __state2;
__current = __this.items[i];
__state = 1;
return true;
__state1:
--i;
goto __loop;
__state2:
__state = 2;
return false;
}

public void Dispose() {
__state = 2;
}

void IEnumerator.Reset() {
throw new NotSupportedException();
}
}
}

В предыдущей трансляции код в блоке итератора преобразован в конечный автомат и помещен в метод MoveNext класса перечислителя. Более того, локальная переменная i преобразована в поле в объекте перечислителя, что делает возможным ее существование при последующих вызовах MoveNext.

Следующий пример служит для печати простой таблицы умножения целых чисел от 1 до 10. В этом примере метод FromTo возвращает перечислимый объект и реализуется с помощью итератора.

using System;
using System.Collections.Generic;

class Test
{
static IEnumerable<int> FromTo(int from, int to) {
while (from <= to) yield return from++;
}

static void Main() {
IEnumerable<int> e = FromTo(1, 10);
foreach (int x in e) {
foreach (int y in e) {
Console.Write("{0,3} ", x * y);
}
Console.WriteLine();
}
}
}

Метод FromTo можно транслировать в экземпляр класса перечислителя, созданного компилятором и инкапсулирующего код в блоке итератора, как показано в следующем примере.

using System;
using System.Threading;
using System.Collections;
using System.Collections.Generic;

class Test
{
...

static IEnumerable<int> FromTo(int from, int to) {
return new __Enumerable1(from, to);
}

class __Enumerable1:
IEnumerable<int>, IEnumerable,
IEnumerator<int>, IEnumerator
{
int __state;
int __current;
int __from;
int from;
int to;
int i;

public __Enumerable1(int __from, int to) {
this.__from = __from;
this.to = to;
}

public IEnumerator<int> GetEnumerator() {
__Enumerable1 result = this;
if (Interlocked.CompareExchange(ref __state, 1, 0) != 0) {
result = new __Enumerable1(__from, to);
result.__state = 1;
}
result.from = result.__from;
return result;
}

IEnumerator IEnumerable.GetEnumerator() {
return (IEnumerator)GetEnumerator();
}

public int Current {
get { return __current; }
}

object IEnumerator.Current {
get { return __current; }
}

public bool MoveNext() {
switch (__state) {
case 1:
if (from > to) goto case 2;
__current = from++;
__state = 1;
return true;
case 2:
__state = 2;
return false;
default:
throw new InvalidOperationException();
}
}

public void Dispose() {
__state = 2;
}

void IEnumerator.Reset() {
throw new NotSupportedException();
}
}
}

Перечислимый класс реализует как перечислимые интерфейсы, так и интерфейсы перечислителя, что позволяет ему служить как перечислимым классом, так и перечислителем. При первом вызове метода GetEnumerator будет возвращен сам перечислимый объект. При последующих вызовах GetEnumerator для перечислимого объекта, при их наличии, будет возвращена копия перечислимого объекта. Таким образом, каждый возвращенный перечислитель имеет собственное состояние, и изменения в одном перечислителе не влияют на другой. Метод Interlocked.CompareExchange используется для обеспечения потокобезопасной операции.

Параметры from и to преобразуются в поля в перечислимом классе. Поскольку параметр from изменяется в блоке итератора, то дополнительно вводится поле __from, которое содержит начальное значение, присваиваемое параметру from в каждом перечислителе.

Метод MoveNext порождает исключение InvalidOperationException, если при его вызове значение __state равно 0. Это позволяет не допустить использования перечислимого объекта в качестве объекта перечислителя без вызова GetEnumerator.

В следующем примере показан класс простого дерева. Класс Tree<T> реализует метод GetEnumerator с помощью итератора. Итератор перечисляет элементы дерева в инфиксном порядке.

using System;
using System.Collections.Generic;

class Tree<T>: IEnumerable<T>
{
T value;
Tree<T> left;
Tree<T> right;

public Tree(T value, Tree<T> left, Tree<T> right) {
this.value = value;
this.left = left;
this.right = right;
}

public IEnumerator<T> GetEnumerator() {
if (left != null) foreach (T x in left) yield x;
yield value;
if (right != null) foreach (T x in right) yield x;
}
}

class Program
{
static Tree<T> MakeTree<T>(T[] items, int left, int right) {
if (left > right) return null;
int i = (left + right) / 2;
return new Tree<T>(items[i],
MakeTree(items, left, i - 1),
MakeTree(items, i + 1, right));
}

static Tree<T> MakeTree<T>(params T[] items) {
return MakeTree(items, 0, items.Length - 1);
}

// The output of the program is:
// 1 2 3 4 5 6 7 8 9
// Mon Tue Wed Thu Fri Sat Sun

static void Main() {
Tree<int> ints = MakeTree(1, 2, 3, 4, 5, 6, 7, 8, 9);
foreach (int i in ints) Console.Write("{0} ", i);
Console.WriteLine();

Tree<string> strings = MakeTree(
"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun");
foreach (string s in strings) Console.Write("{0} ", s);
Console.WriteLine();
}
}

Метод GetEnumerator можно транслировать в экземпляр класса перечислителя, созданного компилятором и инкапсулирующего код в блоке итератора, как показано в следующем примере.

class Tree<T>: IEnumerable<T>
{
...

public IEnumerator<T> GetEnumerator() {
return new __Enumerator1(this);
}

class __Enumerator1 : IEnumerator<T>, IEnumerator
{
Node<T> __this;
IEnumerator<T> __left, __right;
int __state;
T __current;

public __Enumerator1(Node<T> __this) {
this.__this = __this;
}

public T Current {
get { return __current; }
}

object IEnumerator.Current {
get { return __current; }
}

public bool MoveNext() {
try {
switch (__state) {

case 0:
__state = -1;
if (__this.left == null) goto __yield_value;
__left = __this.left.GetEnumerator();
goto case 1;

case 1:
__state = -2;
if (!__left.MoveNext()) goto __left_dispose;
__current = __left.Current;
__state = 1;
return true;

__left_dispose:
__state = -1;
__left.Dispose();

__yield_value:
__current = __this.value;
__state = 2;
return true;

case 2:
__state = -1;
if (__this.right == null) goto __end;
__right = __this.right.GetEnumerator();
goto case 3;

case 3:
__state = -3;
if (!__right.MoveNext()) goto __right_dispose;
__current = __right.Current;
__state = 3;
return true;

__right_dispose:
__state = -1;
__right.Dispose();

__end:
__state = 4;
break;

}
}
finally {
if (__state < 0) Dispose();
}
return false;
}

public void Dispose() {
try {
switch (__state) {

case 1:
case -2:
__left.Dispose();
break;

case 3:
case -3:
__right.Dispose();
break;

}
}
finally {
__state = 4;
}
}

void IEnumerator.Reset() {
throw new NotSupportedException();
}
}
}

Создаваемые компилятором временные данные, используемые в операторах foreach, переносятся в поля __left и __right объекта перечислителя. Поле __state объекта перечислителя обновляется таким образом, чтобы при создании исключения был правильно вызван необходимый метод Dispose(). Обратите внимание, что написание транслируемого кода с простыми операторами foreach невозможно.

Асинхронные функции

Асинхронной функцией называется метод (§10.6) или анонимная функция (§7.15) с модификатором async. В общем смысле, термин асинхронный используется для описания любого вида функции с модификатором async.

Если в списке формальных параметров асинхронной функции указаны параметры ref или out, возникает ошибка времени компиляции.

Типом возвращаемого значения асинхронного метода должен быть void или тип задачи. Типы задач — System.Threading.Tasks.Task и типы, сформированные из System.Threading.Tasks.Task<T>. Эти типы в этой главе для краткости обозначаем как Task и Task<T> соответственно. Асинхронный метод, возвращающий тип задачи, называется возвращающим задачу.

Точное определение типов задач зависит от конкретной реализации, но с точки зрения языка, тип задачи — это одно из следующих состояний: incomplete, succeeded или faulted. Задача faulted записывает соответствующее исключение. Успешно выполненная задача Task<T> записывает тип результата T. Типы задач поддерживают ожидание и поэтому могут быть операндами выражений await (§7.7.7).

Асинхронная функция может приостановить вычисление с помощью выражений await (§7.7.7), содержащихся в ее теле. Позднее вычисление может быть возобновлено с точки приостанавливающего выражения await посредством делегата возобновления. Делегат возобновления — это тип System.Action; при его вызове вычисление, выполняемое асинхронной функцией, возобновляется с выражения await, в котором произошла приостановка. Текущим вызывающим объектом асинхронной функции является исходный вызывающий объект, если выполнение функции не приостанавливалось, или последний вызывающий объект делегата возобновления, если приостановка имела место.

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