Visual Basic, .NET, ASP, VBScript
 

   
 
Описание для автора не найдено
 
     
   
 
21. Анонимные методы
Перевод: Шатохина Надежда(sna@uneta.org), Ukraine .Net Alliance (http://www.uneta.org/)
Июль 2003
Применяется к:
  • Microsoft C#
  • Обзор:
    Спецификация C#. Глава 21: Анонимные методы. В этой главе детально описаны анонимные методы.
    Содерджание:
  • 21.1 Выражения анонимных методов
  • 21.2 Сигнатуры анонимных методов
  • 21.3 Преобразования анонимных методов
  • 21.3.1 Выражение создания делегата
  • 21.4 Блоки анонимных методов
  • 21.5 Внешние переменные
  • 21.5.1 Захваченные внешние переменные
  • 21.5.2 Создание экземпляров локальных переменных
  • 21.6 Обработка анонимного метода
  • 21.7 Равенство экземпляров делегата
  • 21.8 Явное задание параметров
  • 21.9 Преобразования групп методов
  • 21.10 Пример реализации
  • 21.1 Выражения анонимных методов

    Выражение анонимного метода (anonymous-method-expression) определяет анонимный метод и, используя метод, вычисляет специальное значение:

    primary-no-array-creation-expression:
    	…
    	anonymous-method-expression
    anonymous-method-expression:
    	delegate   anonymous-method-signatureopt   block
    anonymous-method-signature:
    	(   anonymous-method-parameter-listopt   )
    anonymous-method-parameter-list:
    	anonymous-method-parameter
    	anonymous-method-parameter-list   ,   anonymous-method-parameter
    anonymous-method-parameter:
    	parameter-modifieropt   type   identifier
    

    Выражение анонимного метода классифицируется как значение со специальными правилами преобразования (§21.3). У значения нет типа, но оно может быть неявно преобразовано в совместимый тип делегата.

    Выражение анонимного метода определяет новое пространство описания для параметров, локальных величин и констант, и новое пространство описания для меток (§3.3).

    21.2 Сигнатуры анонимных методов

    Необязательная сигнатура анонимного метода (anonymous-method-signature) определяет имена и типы формальных параметров для анонимного метода. Область видимости параметров анонимного метода — блок (block). При совпадении имени параметра анонимного метода с именем локальной переменной, локальной константы или параметра, чья область видимости включает выражение анонимного метода (anonymous-method-expression), возникает ошибка компиляции.

    Если в выражении анонимного метода есть сигнатура анонимного метода, тогда набор совместимых типов делегатов ограничивается теми, которые имеют такие же типы параметров и модификаторы в том же порядке (§21.3). Если в выражении анонимного метода нет сигнатуры анонимного метода, тогда набор совместимых типов делегатов ограничивается теми, у которых нет параметров out.

    Обратите внимание, что сигнатура анонимного метода не может включать атрибуты или массив параметров. Тем не менее, сигнатура анонимного метода может быть совместимой с типом делегата, в список параметров которого входит массив параметров.

    21.3 Преобразования анонимных методов

    Выражение анонимного метода (anonymous-method-expression) классифицируется как значение без типа. Выражение анонимного метода может использоваться в выражении создания делегата (delegate-creation-expression) (§21.3.1). Все остальные возможные применения выражения анонимного метода зависят от определенных здесь неявных преобразований.

    Существует неявное преобразование (§6.1) из выражения анонимного метода в любой совместимый тип делегата. Если D — тип делегата, и Aвыражение анонимного метода, тогда D совместим с A, только если выполняются следующие два условия.

    • Во-первых, типы параметров D должны быть совместимыми с A:
      • Если A не содержит сигнатуры анонимного метода (anonymous-method-signature), тогда D может иметь нуль или более параметров любого типа, до тех пор, пока ни у одного параметра в D нет модификатора параметра out.
      • Если A имеет сигнатуру анонимного метода, тогда D должен иметь такое же количество параметров, каждый параметр A должен быть того же типа, что и соответствующий параметр D, и наличие или отсутствие модификатора ref или out в каждом параметре A должно совпадать с соответствующим параметром D. То, является ли последний параметр Dмассивом параметров (parameter-array), не влияет на совместимость A и D.
    • Во-вторых, возвращаемый D тип должен быть совместимым с A. Здесь предполагается, что A не содержит блока (block) каких-либо других анонимных методов.
      • Если D объявлен с возвращаемым типом void, тогда любой содержащийся в A оператор return может не определять выражение.
      • Если D объявлен с возвращаемым типом R, тогда любой содержащийся в A оператор return должен определять выражение, неявно преобразуемое (§6.1) в R. Более того, конечная точка (end-point) блокаA не должна быть достижимой.

    Других преобразований из выражения анонимного метода, кроме неявных преобразований в совместимые типы делегатов, не существует, даже в тип object.

    Следующие примеры иллюстрируют эти правила:

    delegate void D(int x);
    D d1 = delegate { };			// Ok
    D d2 = delegate() { };			// Ошибка, сигнатура не совпадает
    D d3 = delegate(long x) { };		// Ошибка, сигнатура не совпадает
    D d4 = delegate(int x) { };			// Ok
    D d5 = delegate(int x) { return; };		// Ok
    D d6 = delegate(int x) { return x; };	// Ошибка, не совпадает возвращаемый тип
    delegate void E(out int x);
    E e1 = delegate { };			// Ошибка, E имеет параметр out
    E e2 = delegate(out int x) { x = 1; };	// Ok
    E e3 = delegate(ref int x) { x = 1; };	// Ошибка, сигнатура не совпадает
    delegate int P(params int[] a);
    P p1 = delegate { };		// Ошибка, достижим конец блока
    P p2 = delegate { return; };	// Ошибка, не совпадает возвращаемый тип
    P p3 = delegate { return 1; };		// Ok
    P p4 = delegate { return "Hello"; };	// Ошибка, не совпадает возвращаемый тип
    P p5 = delegate(int[] a) {			// Ok
    	return a[0];
    };
    P p6 = delegate(params int[] a) {		// Ошибка, модификатор params
    	return a[0];
    }; 
    P p7 = delegate(int[] a) {			// Ошибка, не совпадает возвращаемый тип
    	if (a.Length > 0) return a[0];
    	return "Hello";
    };
    delegate object Q(params int[] a);
    Q q1 = delegate(int[] a) {			// Ok
    	if (a.Length > 0) return a[0];
    	return "Hello";
    };
    
    21.3.1 Выражение создания делегата

    Выражение создания делегата (delegate-creation-expression) (§7.5.10.3) может использоваться как альтернативный синтаксис для преобразования анонимного метода в тип делегата. Если выражение (expression), используемое как аргумент выражения создания делегата, является выражением анонимного метода (anonymous-method-expression), тогда анонимный метод преобразуется в данный тип делегата с помощью определенных выше правил неявного преобразования. Например, если D — тип делегата, тогда выражение

    new D(delegate { Console.WriteLine("hello"); })
    

    эквивалентно выражению

    (D) delegate { Console.WriteLine("hello"); }
    
    21.4 Блоки анонимных методов

    Блок (block) выражения анонимного метода (anonymous-method-expression) подчиняется следующим правилам:

    • Если анонимный метод включает сигнатуру, параметры, определенные в сигнатуре, доступны в блоке. Если у анонимного метода нет сигнатуры, он может быть преобразован в тип делегата с параметрами (§21.3), но параметры не могут быть доступными в блоке.
    • За исключением обращений к параметрам ref или out, определенным в сигнатуре (если таковая имеется) ближайшего анонимного метода, при обращении блока к параметрам ref или out возникает ошибка компиляции.
    • Когда типом this является структура, при обращении блока к this возникает ошибка компиляции. Это правило выполняется независимо от того, является ли доступ явным (как в this.x) или неявным (как в x, где x — член экземпляра структуры). Это правило просто запрещает такой доступ и не влияет на какие-либо результаты поиска членов в члене структуры.
    • Блок имеет доступ к внешним переменным (§21.5) анонимного метода. При доступе к внешней переменной будет использоваться экземпляр переменной, который является активным во время вычисления выражения анонимного метода (anonymous-method-expression) (§21.6).
    • Если блок содержит операторы goto, break или continue, чья цель находится вне блока или в блоке внешнего анонимного метода, возникает ошибка компиляции.
    • Оператор return в блоке возвращает элемент управления из вызова ближайшего внешнего анонимного метода, а не из члена включающей его функции. Выражение, определенное в операторе return, должно быть совместимым с типом делегата, в который преобразовывается ближайшее внешнее выражение анонимного метода (anonymous-method-expression) (§21.3).

    Явно не определено существование какого-либо еще способа выполнения блока анонимного метода, кроме как через вычисление и вызов выражения анонимного метода (anonymous-method-expression). В частности, компилятор может выбрать реализацию анонимного метода путем синтеза одного или более названных методов или типов. Имена любых таких синтезированных элементов должны находиться в пространстве, зарезервированном для использования компилятором: они должны содержать два последовательных подчеркнутых символа.

    21.5 Внешние переменные

    Любая локальная переменная, параметр значения или массив параметров, область действия которых включает выражение анонимного метода (anonymous-method-expression), называются внешней переменной (outer variable) выражения анонимного метода. В члене функции экземпляра класса значение this считается параметром значения и является внешней переменной любого выражения анонимного метода, содержащегося в члене функции.

    21.5.1 Захваченные внешние переменные

    Когда анонимный метод ссылается на внешнюю переменную, говорят, что внешняя переменная захвачена (captured) анонимным методом. Обычно время жизни локальной переменной ограничено временем выполнения блока или оператора, с которым она ассоциирована (§5.1.7). Однако время жизни захваченной внешней переменной продлевается, по крайней мере, до тех пор, пока делегат, ссылающийся на анонимный метод, не будет собран сборщиком мусора.

    В примере

    using System;
    delegate int D();
    class Test
    {
    	static D F() {
    		int x = 0;
    		D result = delegate { return ++x; }
    		return result;
    	}
    	static void Main() {
    		D d = F();
    		Console.WriteLine(d());
    		Console.WriteLine(d());
    		Console.WriteLine(d());
    	}
    }
    

    локальная переменная x захватывается анонимным методом, и ее время жизни продлевается, по крайней мере, до тех пор, пока делегат, возвращенный от F, не будет собран сборщиком мусора (что не произойдет до самого окончания программы). Поскольку каждый вызов анонимного метода оперирует одним тем же экземпляром x, выходные данные примера следующие:

    1
    2
    3
    

    Когда локальная переменная или параметр значения захватывается анонимным методом, локальная переменная или параметр больше не рассматриваются как фиксированная переменная (§18.3), а считаются подвижной переменной (moveable variable). Таким образом, любой небезопасный код, который принимает адрес захваченной внешней переменной, сначала должен использовать оператор fixed, чтобы зафиксировать переменную.

    21.5.2 Создание экземпляров локальных переменных

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

    static void F() {
    	for (int i = 0; i < 3; i++) {
    		int x = i * 2 + 1;
    		...
    	}
    }
    

    Однако перемещение описания x за границы цикла приводит к тому, что экземпляр x создается всего один раз:

    static void F() {
    	int x;
    	for (int i = 0; i < 3; i++) {
    		x = i * 2 + 1;
    		...
    	}
    }
    

    Обычно невозможно отследить точно, как часто создается экземпляр локальной переменной — поскольку времена жизни экземпляров не пересекаются, каждый экземпляр может просто использовать один и тот же адрес ячейки памяти. Однако, когда анонимный метод захватывает локальную переменную, результаты создания экземпляров становятся явными. Пример

    using System;
    delegate void D();
    class Test
    {
    	static D[] F() {
    		D[] result = new D[3];
    		for (int i = 0; i < 3; i++) {
    			int x = i * 2 + 1;
    			result[i] = delegate { Console.WriteLine(x); };
    		}
    		return result;
    	}
    	static void Main() {
    		foreach (D d in F()) d();
    	}
    }
    

    формирует выход:

    1
    3
    5
    

    Однако когда описание x вынесено за рамки цикла:

    static D[] F() {
    	D[] result = new D[3];
    	int x;
    	for (int i = 0; i < 3; i++) {
    		x = i * 2 + 1;
    		result[i] = delegate { Console.WriteLine(x); };
    	}
    	return result;
    }
    

    результат такой:

    5
    5
    5
    

    Обратите внимание, что три делегата, созданные во второй версии F, будут равны, согласно оператору равенства (§21.7). Более того, заметьте, что компилятору позволено (но от него не требуется) оптимизировать три экземпляра в один экземпляр делегата (§21.6).

    Делегаты анонимных методов могут совместно использовать некоторые захваченные переменные, имея при этом и отдельные экземпляры других захваченных переменных. Например, если F меняется на

    static D[] F() {
    	D[] result = new D[3];
    	int x = 0;
    	for (int i = 0; i < 3; i++) {
    		int y = 0;
    		result[i] = delegate { Console.WriteLine("{0} {1}", ++x, ++y); };
    	}
    	return result;
    }
    

    три делегата захватывают один и тот же экземпляр x, но разные экземпляры y, и результат таков:

    1 1
    2 1
    3 1
    

    Отдельные анонимные методы могут захватывать один и тот же экземпляр внешней переменной. В примере:

    using System;
    delegate void Setter(int value);
    delegate int Getter();
    class Test
    {
    	static void Main() {
    		int x = 0;
    		Setter s = delegate(int value) { x = value; };
    		Getter g = delegate { return x; };
    		s(5);
    		Console.WriteLine(g());
    		s(10);
    		Console.WriteLine(g());
    	}
    }
    

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

    5
    10
    
    21.6 Обработка анонимного метода

    В результате динамической обработки выражения анонимного метода (anonymous-method-expression) получается экземпляр делегата, который ссылается на анонимный метод и (возможно, пустой) набор захваченных внешних переменных, которые активны во время обработки. При вызове полученного из выражения анонимного метода делегата выполняется тело анонимного метода. Код в теле выполняется с использованием набора захваченных внешних переменных, на которые ссылается делегат.

    Список вызова полученного из выражения анонимного метода содержит одну точку входа. Точные объект-цель и метод-цель делегата не указаны. В частности, не указано, имеет ли объект-цель делегата значение null, значение this члена включающей его функции или является каким-то другим объектом.

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

    delegate double Function(double x);
    class Test
    {
    	static double[] Apply(double[] a, Function f) {
    		double[] result = new double[a.Length];
    		for (int i = 0; i < a.Length; i++) result[i] = f(a[i]);
    		return result;
    	}
    	static void F(double[] a, double[] b) {
    		a = Apply(a, delegate(double x) { return Math.Sin(x); });
    		b = Apply(b, delegate(double y) { return Math.Sin(y); });
    		...
    	}
    }
    

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

    21.7 Равенство экземпляров делегата

    Операторы равенства (§7.9.8) и метод Object.Equals для экземпляров делегатов анонимных методов подчиняются следующим правилам:

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

    21.8 Явное задание параметров

    Явное задание параметра анонимного метода осуществляется аналогично заданию параметра для именованного метода. То есть используемые параметры и значащие параметры изначально заданы явно, а выходные параметры не заданы. Более того, выходные параметры обычно должны быть явно заданы до возвращения анонимного метода (§5.1.6).

    Заданное явно значение внешней переменной v при передаче управления блоку (block) выражения анонимного метода (anonymous-method-expression) такое же, как и перед выражением анонимного метода. То есть явное задание внешних переменных наследуется от контекста выражения анонимного метода. В блоке выражения анонимного метода явное задание обрабатывается так же, как и в обычном блоке (§5.3.3).

    Заданное явно значение переменной v после выражения анонимного метода такое же, как и перед выражением анонимного метода.

    Пример

    delegate bool Filter(int i);
    void F() {
    	int max;
    	// Ошибка, max не задана явно 
    	Filter f = delegate(int n) { return n < max; }
    	max = 5;
    	DoWork(f);
    }
    

    формирует ошибку компиляции, поскольку max не задана явно там, где объявляется анонимный метод. Пример

    delegate void D();
    void F() {
    	int n;
    	D d = delegate { n = 1; };
    	d();
    	// Ошибка, n не задана явно
    	Console.WriteLine(n);
    }
    

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

    21.9 Преобразования групп методов

    Аналогично неявным преобразованиям анонимных методов, описанным в §21.3, существует неявное преобразование из группы методов (§7.1) в совместимый тип делегата.

    Заданы группа методов E и тип делегата D, если допустимо выражение создания делегата (§7.5.10.3 и §20.9.6) в форме new D(E), тогда неявное преобразование из E в D также существует, и результат этого преобразования точно эквивалентен написанию new D(E).

    В примере

    using System;
    using System.Windows.Forms;
    class AlertDialog
    {
    	Label message = new Label();
    	Button okButton = new Button();
    	Button cancelButton = new Button();`
    	public AlertDialog() {
    		okButton.Click += new EventHandler(OkClick);
    		cancelButton.Click += new EventHandler(CancelClick);
    		...
    	}
    	void OkClick(object sender, EventArgs e) {
    		...
    	}
    	void CancelClick(object sender, EventArgs e) {
    		...
    	}
    }
    

    используя оператор new, конструктор создает два экземпляра делегата. Неявные преобразования групп методов позволяет записать это в более краткой форме:

    public AlertDialog() {
    	okButton.Click += OkClick;
    	cancelButton.Click += CancelClick;
    	...
    }
    

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

    object obj = new EventHandler(myDialog.OkClick);
    

    может быть записан так

    object obj = (EventHandler)myDialog.OkClick;
    

    Группы методов и выражения анонимных методов могут оказывать влияние на анализ перегрузки, но не участвуют в процессе логического определения типа. Более детально смотрите в §20.6.4.

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

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

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

    public delegate void D();
    

    Самая простая форма анонимного метода — та, которая не захватывает внешние переменные:

    class Test
    {
    	static void F() {
    		D d = delegate { Console.WriteLine("test"); };
    	}
    }
    

    Это может быть преобразовано в экземпляр делегата, который использует сгенерированный компилятором статический метод, в котором размещен код анонимного метода:

    class Test
    {
    	static void F() {
    		D d = new D(__Method1);
    	}
    	static void __Method1() {
    		Console.WriteLine("test");
    	}
    }
    

    В следующем примере анонимный метод ссылается на члены экземпляра this:

    class Test
    {
    	int x;
    	void F() {
    		D d = delegate { Console.WriteLine(x); };
    	}
    }
    

    Этот код может быть преобразован в сгенерированный компилятором метод экземпляра, содержащий код анонимного метода:

    class Test
    {
    	int x;
    	void F() {
    		D d = new D(__Method1);
    	}
    	void __Method1() {
    		Console.WriteLine(x);
    	}
    }
    

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

    class Test
    {
    	void F() {
    		int y = 123;
    		D d = delegate { Console.WriteLine(y); };
    	}
    }
    

    Теперь время жизни локальной переменной должно продлиться, по крайней мере, до времени жизни делегата анонимного метода. Этого можно достигнуть, «перетянув» локальную переменную в поле сгенерированного компилятором класса. Тогда момент создания экземпляра локальной переменной (§21.5.2) совпадает с созданием экземпляра сгенерированного компилятором класса, и обращение к локальной переменной соответствует обращению к полю в экземпляре сгенерированного компилятором класса. Более того, анонимный метод становится методом экземпляра сгенерированного компилятором класса.

    class Test
    {
    	void F() {
    		__locals1 = new __Locals1();
    		__locals1.y = 123;
    		D d = new D(__locals1.__Method1);
    	}
    	class __Locals1
    	{
    		public int y;
    		public void __Method1() {
    			Console.WriteLine(y);
    		}
    	}
    }
    

    И, наконец, следующий анонимный метод захватывает this и две локальные переменные с различной продолжительностью жизни:

    class Test
    {
    	int x;
    	void F() {
    		int y = 123;
    		for (int i = 0; i < 10; i++) {
    			int z = i * 2;
    			D d = delegate { Console.WriteLine(x + y + z); };
    		}
    	}
    }
    

    Здесь сгенерированный компилятором класс создается для каждого блока выражения, в котором локальные переменные захватываются таким образом, что их времена жизни в различных блоках независимы. Экземпляр __Locals2, сгенерированный компилятором класс для внутреннего блока выражения, содержит локальную переменную z и поле, ссылающееся на экземпляр __Locals1. Экземпляр __Locals2, сгенерированный компилятором класс для внешнего блока выражения, содержит локальную переменную y и поле, ссылающееся на this члена включающей его функции. С помощью этих структур данных ко всем захваченным внешним переменным можно обратиться через экземпляр __Local2, и код анонимного метода, таким образом, может быть реализован как метод экземпляра этого класса.

    class Test
    {
    	void F() {
    		__locals1 = new __Locals1();
    		__locals1.__this = this;
    		__locals1.y = 123;
    		for (int i = 0; i < 10; i++) {
    			__locals2 = new __Locals2();
    			__locals2.__locals1 = __locals1;
    			__locals2.z = i * 2;
    			D d = new D(__locals2.__Method1);
    		}
    	}
    	class __Locals1
    	{
    		public Test __this;
    		public int y;
    	}
    	class __Locals2
    	{
    		public __Locals1 __locals1;
    		public int z;
    		public void __Method1() {
    			Console.WriteLine(__locals1.__this.x + __locals1.y + z);
    		}
    	}
    }
    
    Никакая часть настоящей статьи не может быть воспроизведена или передана в какой бы то ни было форме и какими бы то ни было средствами, будь то электронные или механические, если на то нет письменного разрешения владельцев авторских прав.

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

       
       
         
      VBNet рекомендует