0语言规范匿名方法,中的多播委托笔记总结【10bet体育中文官网】
分类:高并发

CLR 中匿名函数的实现原理浅析

 第三章 匿名方法
原著:Microsoft Corporation
原文: (SpecificationVer2.doc)
翻译:lover_P
出处:

//以下示例和说明都源于《visual c# 2005 技术内幕》
//匿名函数就是没有名字的函数,是专用于委托的函数。

定义:
委托是一种在对象里保存方法引用的类型,同时也是一种类型安全的函数指针。
理解委托的一种方式可以把委托的作用当作是给方法签名指定名称。
委托的定义类似于方法的定义,但没有方法体,定义的委托名前要加上关键字delegate。

C# 2.0中提供了通过delegate实现匿名函数功能,能有效地减少用户的薄记代码工作,例如


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

因为定义委托基本上是定义一个新类,所以可以在定义类的任何地方定义委托,既可以在另一个类的内部定义委托,也可以在所有类的外部定义委托,还可以在命名空间中把委托定义为顶层对象。根据定义的可见性,可以在委托定义上添加一般的访问修饰符:public、private和protected等:

以下为引用:

[内容]

namespace 匿名方法
{
    public delegate void DelegateClass();
    public delegate void delegatec(out int param);
    public delegate void ADelegate<T>(T tvalue);
    class Program
    {
        static void Main(string[] args)
        {
            int m;
           
            //定义了一个无签名的地匿名函数,该匿名方法的签名是从委托推导出来的。
            //但它的返回类型必须和委托相同。
            DelegateClass del = delegate
            {
                Console.WriteLine("Running anonyous method");
            };
            del();
            //定义具有一个签名的匿名函数
            delegatec dell = delegate(out int inner)
            {
                inner = 12;
                Console.WriteLine(inner);
            };
            dell(out m);
            //外部变量。 匿名方法可以对该方法定义的作用域的包含函数和类成员的局部变量进行引用。
            //在匿名方法中使用的局部变量称为外部变量。
            //一个被捕获的外部变量的生存周期与委托的生存周期相同
            dell = MethodA();
            dell(out m);
            Console.WriteLine(m);

一个委托实例压缩(或者称为封装)了一个方法称之为“一个可调用的实体” ,所以“一个可调用的实体”也就由这个委托实例本身和实例中封装的方法组成

...
button1.Click += new EventHandler(button1_Click);
...
void button1_Click(Object sender, EventArgs e) {
// Do something, the button was clicked...
}
...

3.1 匿名方法表达式
3.2 匿名方法签名
3.3 匿名方法转换
3.3.1 委托建立表达式
3.4 匿名方法块
3.5 外部变量
3.5.1 捕获外部变量
3.5.2 局部变量的实例化
3.6 匿名方法求值
3.7 委托实例相等性
3.8 明确赋值
3.9 方法组转换
3.10 实现实例

            //范型匿名方法。匿名方法可以使用指定类或委托的范型参数,但是匿名方法不能定义新的范型参数和约束。
            ADelegate<int> ad = delegate(int a) {
                a = 10;
                Console.WriteLine(a);
            };
            ad(m);
        }
        public static delegatec MethodA()
        {
            int local = 0;
            return delegate(out int arg)//返回匿名方法
            {
                arg = ++local;//延长了局部变量local的生存周期
            };
        }
    }
}
//注:匿名方法的限制:
//不要企图跳出一个匿名方法,
//不要在一个匿名方法中使用一个ref或者out类型的外部变量
//不要定义新的一般参数或约束
//不要将属性应用于匿名方法
//不要用-= 赋值运算符使用匿名方法
//不能是一个成员方法
//不能是一个不安全方法

委托也可以包含多个方法,这种委托称为多播委托。

可以被简化为直接使用匿名函数构造,如

3.1 匿名方法表达式
   匿名方法表达(anonymous-method-expression)式定义了匿名方法(anonymous method),并求得一个引用了该方法的特殊的值。

阅读全文
类别:默认分类 查看评论

如果调用多播委托,就可以按顺序连续调用多个方法。为此,委托的签名就必须返回 void (否则,返回值应送到何处?)(当委托只包含一个方法的时候,其返回类型的声明可以参照所封装的方法,不一定必须是void)。实际上,如果编译器发现某个委托返回 void ,就会自动假定这是一个多播委托。

以下为引用:

primary-no-array-creation-expression:
   …
   anonymous-method-expression
anonymous-method-expression:
   delegate   anonymous-method-signatureopt   block

委托的优点:
(1)压缩方法的调用。

...
button1.Click += delegate(Object sender, EventArgs e) {
// Do something, the button was clicked...
}
...

anonymous-method-signature:
   (   anonymous-method-parameter-listopt   )

(2)合理有效地使用委托能提升应用程序的性能。

关于匿名函数的使用方法可以参考Jeffrey Richter的Working with Delegates Made Easier with C# 2.0一文。简要说来就是C#编译器自动将匿名函数代码转移到一个自动命名函数中,将原来需要用户手工完成的工作自动完成。例如构造一个私有静态函数,如

anonymous-method-parameter-list:
   anonymous-method-parameter
   anonymous-method-parameter-list   ,   anonymous-method-parameter

(3)用于调用匿名方法。

以下为引用:

anonymous-method-parameter:
   parameter-modifieropt   type   identifier

委托的声明:

class AClass {
static void CallbackWithoutNewingADelegateObject() {
ThreadPool.QueueUserWorkItem(delegate(Object obj) { Console.WriteLine(obj); }, 5);
}
}

初等非数组表达式:
   ...
   匿名方法表达式

作用域修饰符 delegate 委托返回类型 委托名();
如:public delegate  Void Test();

被编译器自动转换为

匿名方法表达式:
   delegate   匿名方法签名可选   块

注意点:可以在不带参数或参数列表的情况下声明委托。应当遵循和声明方法一样的语法来声明委托.

以下为引用:

匿名方法签名:
   (   匿名方法参数列表可选   )

委托的示例程序:

class AClass {
static void CallbackWithoutNewingADelegateObject() {
ThreadPool.QueueUserWorkItem(new WaitCallback(__AnonymousMethod$00000002), 5);
}

匿名方法参数列表:
   匿名方法参数
   匿名方法参数   ,   匿名方法参数

public delegate double Delegate_Prod(int a, int b);
class Class1
{
    static double fn_Prodvalues(int val1, int val2)
    {
        return val1 * val2;
    }
    static void Main(string[] args)
    {
        //Creating the Delegate Instance
        Delegate_Prod delObj = new Delegate_Prod(fn_Prodvalues);
        Console.Write("Please Enter Values");
        int v1 = Int32.Parse(Console.ReadLine());
        int v2 = Int32.Parse(Console.ReadLine());
        //use a delegate for processing
        double res = delObj(v1, v2);
        Console.WriteLine("Result :" + res);
        Console.ReadLine();
    }
}

解释:

private static void __AnonymousMethod$00000002(Object obj) {
Console.WriteLine(obj);
}
}

匿名方法参数:
   参数修饰符可选   类型   标识符

上面我用一段小程序示范了委托的使用。委托Delegate_Prod声明时指定了两个只接受整型变量的返回类型。同样类中名为fn_Prodvalues的方法也是如此,委托和方法具有相同的签名和参数类型。

而这里自动生成的函数是否为static,编译器根据使用此函数的地方是否static决定。这也是为什么C# 2.0规范里面禁止使用goto, break和continue语句从一个匿名方法里跳出,或从外面跳入其中的原因,因为他们代码虽然写在一个作用域里面,但实际上实现上并不在一起。
更方便的是编译器可以根据匿名函数使用的情况,自动判断函数参数,无需用户在定义时指定,如

   匿名方法表达(anonymous-method-expression)是一个遵从特殊转换规则(见3.3)的值。这个值没有类型,但可以隐式地转换为一个兼容的委托类型。

在Main方法中创建一个委托实例并用如下方式将函数名称传递给该委托实例:

以下为引用:

   匿名方法表达式(anonymous-method-expression)为参数、局部变量和常量以及标签定义了一个新的声明空间。

Delegate_Prod delObj = new Delegate_Prod(fn_Prodvalues);

button1.Click += delegate(Object sender, EventArgs e) { MessageBox.Show("The Button was clicked!"); };

3.2 匿名方法签名
   可选的匿名方法签名(anonymous-method-signature)为匿名方法的形式参数定义了名字和类型。这些参数的作用于是匿名方法的块(block)。如果一个局部变量的作用域包含了匿名方法表达式(anonymous-method-expression),且该匿名方法的参数和该局部变量相同,则会产生编译错误。

这样我们就接受了来自用户的两个值并将其传递给委托:

在不使用参数时,完全等价于

   如果一个匿名方法表达式(anonymous-method-expression)具有匿名方法签名(anonymous-method-signature),则与之兼容的委托类型被强制具有相同的参数类型和修饰符,且具有相同顺序(见3.3)。如果一个匿名方法表达式(anonymous-method-expression)没有匿名方法签名(anonymous-method-signature),则与之相兼容的委托类型被强制要求没有out参数。

delObj(v1,v2);

以下为引用:

   注意匿名方法签名(anonymous-method-signature)不能包含特性或参数数组(译注:用于实现变长参数列表)。然而,一个匿名方法签名(anonymous-method-signature)可以和一个包含参数数组的委托类型相兼容。

在此委托对象压缩了方法的功能并返回我们在方法中指定的结果。

button1.Click += delegate { MessageBox.Show("The Button was clicked!"); };

3.3 匿名方法转换
   匿名方法表达式(anonymous-method-expression)是一个没有类型的特殊值。一个匿名方法表达式(anonymous-method-expression)可以用于委托建立表达式(delegate-creation-expression)(见3.3.1)。对于匿名方法表达式(anonymous-method-expression)的其他有效的应用取决于定义于其上的隐式转换。

多播委托:

相对于匿名函数的实现来说,比较复杂的是匿名函数对于其父作用域中变量的使用及其实现。MS的Grant Ri在其blog上有一系列的讨论文章。
Anonymous Methods, Part 1 of ?
Anonymous Methods, Part 2 of ?
Anonymous Method Part 2 answers

   匿名方法表达式(anonymous-method-expressio)与任何兼容的委托类型之间均存在隐式转换。如果D是一个委托类型,而A是一个匿名方法表达式(anonymous-method-expression),当且仅当以下两个条件成立的时候D和A是兼容的。

(1)多播委托包含一个以上方法的引用。

需要解决的问题有两个:一是不在一个变量作用域中的匿名函数如何访问父函数和类的变量;二是匿名函数使用到的变量的生命周期必须与其绑定,而不能与父函数的调用生命周期绑定。这两个问题使得C#编译器选择较为复杂的独立类封装方式实现匿名函数和相关变量生命周期的管理。

首先,D的参数类型必须与A兼容:
   如果A不含匿名方法签名(anonymous-method-signature),则D可以具有任意类型的零或多个参数,但这些参数不能带有out修饰符。

(2)多播委托包含的方法必须返回void,否则会抛出run-time exception。

首先,匿名函数使用到的父函数中局部变量,无聊是引用类型还是值类型,都必须从栈变量转换为堆变量,以便在其作用域外的匿名函数实现代码可以访问并控制生命周期。因为栈变量的生命周期与其所有者函数是一致的,所有者函数退出后,其堆栈自动恢复到调用函数前,也就无法完成变量生命周期与函数调用生命周期的解耦。
例如下面这个简单的匿名函数中,使用了父函数的局部变量,虽然此匿名函数只在父函数里面使用,但C#编译器还是使用独立类对其使用到的变量进行了包装。

   如果具有匿名方法签名(anonymous-method-signature),则D必须具有和A形同数量的参数,A中的每个参数必须和D中相应的参数具有相同的类型,并且A中每个参数上的ref或out修饰符的出现与否必须与D中的相应参数相同。如果D中的最后一个参数是参数数组,则没有相互兼容的A和D。

多播委托的示例程序:
using System;
using System.Collections.Generic;
using System.Text;

以下为引用:

其次,D的返回值类型必须与A兼容。由于这一规则,A中不能包含其他匿名方法的块(block)。
   如果D的返回值类型被声明为void,则A中包含的所有return语句不能指定表达式。

namespace ConsoleApp1
{

delegate void Delegate1();

   如果D的返回值类型被声明为R,则A中包含的所有return语句不许指定一个能够隐式转换为R的表达式。A中的块(block)的终点必须可达。

    //声明一个委托
    public delegate void msg();

public void Method1()
{
int i=0;

   除了和相兼容的委托类型之间的隐式转换,匿名方法表达式(anonymous-method-expression)与任何类型之间不存在任何转换,包括object类型。

    //编写一个类
    class messges
    {
        //messges类下的一个成员方法m1
        public static void m1()
        { Console.WriteLine("方法一被huashanlin调用"); }

Delegate1 d1 = delegate() { i++; };

   下面的例子详细地解释了这些规则:

        //messges类下的一个成员方法m2
        public static void m2()
        { Console.WriteLine("方法二被huashanlin调用"); }

d1();
}

delegate void D(int x);

        //messges类下的一个成员方法m3
        public static void m3()
        { Console.WriteLine("方法3huashanlin被调用"); }
    }

自动生成的包装代码类似如下

D d1 = delegate { };                    // 正确
D d2 = delegate() { };                  // 错误,签名不匹配
D d3 = delegate(long x) { };            // 错误,签名不匹配
D d4 = delegate(int x) { };             // 正确
D d5 = delegate(int x) { return; };     // 正确
D d6 = delegate(int x) { return x; };   // 错误,返回值不匹配

    //另一个类
    class Program
    {
       
        //该类下包含的主函数
        static void Main(string[] args)
        {
            //实例化一个委托,并封装messges类中的一个方法m2
            msg ms = new msg(messges.m2);

以下为引用:

delegate void E(out int x);

            //在原有的封装了一个m1方法的委托实例中再封装进一个新的方法m1
            ms = ms + messges.m1;
            //或者上面的语句可以写成ms += messges.m1;两者的效果是一样的

delegate void Delegate1();

E e1 = delegate { };                    // 错误,E带有一个输出参数
E e2 = delegate(out int x) { x = 1; };  // 正确
E e3 = delegate(ref int x) { x = 1; };  // 错误,签名不匹配

            //以下为该委托实例封装第三个方法
            ms += messges.m3;

private sealed class __LocalsDisplayClass$00000002
{
public int i;

delegate int P(params int[] a);

            //调用该委托实例,那么由于多播委托之后调用该委托就要执行完此封装进去的三个方法
            ms();

public void __AnonymousMethod$00000001()
{
this.i++;
}
};

P p1 = delegate { };                    // 错误,块的结尾不可达
P p2 = delegate { return; };            // 错误,返回值类型不匹配
P p3 = delegate { return 1; };          // 正确
P p4 = delegate { return "Hello"; };    // 错误,返回值类型不匹配

        }
    }
}

public void Method1()
{
__LocalsDisplayClass$00000002 local1 = new __LocalsDisplayClass$00000002();
local1.i = 0;

P p5 = delegate(int[] a) {              // 正确
   return a[0];
};

//注:deletge保存的方法签名是以hash队列的形式存在的

Delegate1 d1 = new Delegate1(local1.__AnonymousMethod$00000001);

P p6 = delegate(params int[] a) {       // 错误,(译注:多余的)params修饰符
   return a[0];
};

多播委托示例程序二:

d1();
}

P p7 = delegate(int[] a) {              // 错误,返回值类型不匹配
   if(a.Length > 0) return a[0];
   return "Hello";
};

delegate void Delegate_Multicast(int x, int y);

但对于有多个局部变量作用域的情况就比较复杂了,例如Grant Ri在其例子中给出的代码

delegate object Q(params int[] a);

Class Class2

以下为引用:

Q q1 = delegate(int[] a) {              // 正确
   if(a.Length > 0) return a[0];
   return "Hello";
};

{
static void Method1(int x, int y) {
  Console.WriteLine("You r in Method 1");
}
static void Method2(int x, int y) {
  Console.WriteLine("You r in Method 2");
}
public static void Main()
{
  Delegate_Multicast func = new Delegate_Multicast(Method1);

delegate void NoArgs();

3.3.1 委托建立表达式
   委托建立表达式(delegate-creation-expression)可以用于匿名方法和委托类型之间的转换语法的替代品。如果一个委托建立表达式(delegate-creation-expression)的参数表达式(expression)是一个匿名方法表达式(anonymous-method-expression),则匿名方法依照上面定义的隐式转换规则转换为给定的委托类型。例如,如果D是一个委托类型,则表达式

func += new Delegate_Multicast(Method2);
      func(1,2);             // Method1 and Method2 are called
      func -= new Delegate_Multicast(Method1);
      func(2,3);             // Only Method2 is called
   }

void SomeMethod()
{
NoArgs [] methods = new NoArgs[10];
int outer = 0;
for (int i = 0; i < 10; i++)
{
int inner = i;
methods[i] = delegate {
Console.WriteLine("outer = {0}", outer++);
Console.WriteLine("i = {0}", i);
Console.WriteLine("inner = {0}", ++inner);
};
methods[i]();
}
for (int j = 0; j < methods.Length; j++)
methods[j]();
}

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

}    

就需要一个类封装变量outer;一个类封装变量i;另外一个类封装inner和匿名函数,并引用前面两个封装类的实例。因为变量outer、i和inner有着不同的作用域,呵呵。伪代码如下:

等价于表达式

解析:

以下为引用:

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

上面的示例程序分别定义了名为method1 和 method2的两个接受整型参数、返回类型为void的方法。

private sealed class __LocalsDisplayClass$00000008
{
public int outer;

3.4 匿名方法块
匿名方法表达式(anonymous-method-expression)的块(block)遵从下列规则:

在Main函数里使用下面的声明创建委托对象:

};
private sealed class __LocalsDisplayClass$0000000a
{
public int i;

如果匿名方法包含一个签名,则签名中指定的参数在块(block)中是可用的。如果匿名方法不包含签名,则它可以转换为一个带有参数的委托类型(见3.3),但这些参数在块(block)中无法访问。
除非在最贴切的匿名方法的签名(如果有的话)中指定了ref或out参数,否则在块中访问ref或out参数会发生编译期间错误。
当this的类型是一个结构类型时,当在块(block)中访问this是一个编译错误,不论这种能够访问是显式的(如this.x)还是隐式的(如x,而x是该结构的一个实例方法)。这一规则简单地禁止了这种访问,从而不会对结构的成员的查找结果产生影响。
块(block)可以访问匿名方法外部的变量(见3.5)。对外部变量的访问将会引用到变量的实例,该变量在匿名方法表达式(anonymous-method-expression)求值的过程中应当是活动的(见3.6)。
如果块(block)中包含的goto语句、break语句或continue语句的目标在块(block)的外面或在块(block)中包含的一个匿名方法中,则会产生编译错误。
块(block)中的return语句将控制从最近的一个匿名方法的调用中返回,而不是从函数成员中返回。return语句中指定的表达式必须和匿名方法表达式(anonymous-method-expression)转换(见3.3)得到的委托类型相匹配。
   除了计算和调用匿名方法表达式(anonymous-method-expression)外,对于块(block)的执行方式没有任何明确的限制。特别地,编译器会选择通过合成个或多个命名了的方法或类型来实现一个匿名方法。这些合成的名字必须保留在编译器所使用的空间中:这些名字必须包含两个连续的下划线。

Delegate_Multicast func = new Delegate_Multicast(Method1);

};
private sealed class __LocalsDisplayClass$0000000c
{
public int inner;

3.5 外部变量
   若一个匿名方法表达式(anonymous-method-expression)包含在任何局部变量、值参数或参数数组的作用域中,则称它们(译注:指那些局部变量、值参数或参数数组)为该匿名方法表达式(anonymous-method-expression)的外部变量(outer variable)。在一个类的实例函数成员中,this被认为是一个值参数,并且是该函数成员中所包含的任何匿名方法表达式(anonymous-method-expression)的外部变量。

然后使用+= 来添加委托,使用-=来移除委托。

public __LocalsDisplayClass$00000008 $locals$00000009;
public __LocalsDisplayClass$0000000a $locals$0000000b;

3.5.1 捕获外部变量
   当在一个匿名方法中引用一个外部变量时,称该外部变量被匿名方法所捕获。通常,一个局部变量的生存期是块或与之关联的语句的执行的结束点。然而,一个被捕获的外部变量的生存期将持续到引用了匿名方法的委托符合垃圾收集的条件时。

public void __AnonymousMethod$00000007()
{
Console.WriteLine("outer = {0}", this.$locals$00000009.outer++);
Console.WriteLine("i = {0}", this.$locals$0000000b.i);
Console.WriteLine("inner = {0}", ++this.inner);
}
};

在下面的例子中:

public void SomeMethod()
{
NoArgs [] methods = new NoArgs[10];

using System;

__LocalsDisplayClass$00000008 local1 = new __LocalsDisplayClass$00000008();
local1.outer = 0;

delegate int D();

__LocalsDisplayClass$0000000a local2 = new __LocalsDisplayClass$0000000a();
local2.i = 0;

class Test {
   static D F() {
       int x = 0;
       D result = delegate { return ++x; }
       return result;
   }

while(local2.i < 10)
{
__LocalsDisplayClass$0000000c local3 = new __LocalsDisplayClass$0000000c();
local3.$locals$00000009 = local1;
local3.$locals$0000000b = local2;
local3.inner = local1.i;

   static void Main() {
       D d = F();
       Console.WriteLine(d());
       Console.WriteLine(d());
       Console.WriteLine(d());
   }
}

methods[local2.i] = new NoArgs(local3.__AnonymousMethod$00000007);
methods[local2.i]();
}

   局部变量x被匿名方法所捕获,x的生存期至少被延续到从F所返回的委托符合垃圾收集条件时(这并不一定发生在程序的最末尾)。由于每个匿名方法的调用均操作了x的同一个实例,这个例子的输出将会是:

for (int j = 0; j < methods.Length; j++)
methods[j]();
}

1
2
3

总结其规律就是每个不同的局部变量作用域会有一个单独的类进行封装,子作用域中如果使用到父作用域的局部变量,则子作用域的封装类引用父作用域的封装类。相同作用域的变量和匿名方法由封装类绑定到一起,维护其一致的生命周期。

   当一个局部变量或一个值参数被一个匿名方法所捕获,该局部变量获知参数将不再被认为是一个固定变量,而是被认为是一个可移动的变量。因此,任何unsafe代码如果记录了该外部变量的地址,则必须首先使用fixed语句来固定这个变量。

相对于MS较为复杂的实现,Delphi.NET对嵌套函数则使用较为简单的参数传递方式,因为嵌套函数没有那么复杂的变量生命期管理要求,如

3.5.2 局部变量的实例化
   当程序执行到一个变量的作用域中时,则该局部变量被实例化。例如,当下面的方法被调用时,局部变量x被实例化和初始化三次——每当循环迭代一次时。

以下为引用:

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

procedure SayHello;
var
Name: string;

   然而,将x的声明移到循环外面则只会引起x的一次实例化:

procedure Say;
begin
WriteLn(Name);
end;
begin
Name := 'Flier Lu';

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

Say;
end;

   通常,没有办法观察到一个局部变量被实例化过多少次——因为实例化的生存期是脱节的,而上每次实例化都简单地使用相同的存贮空间是可能的。然而,当一个匿名方法捕获了一个局部变量,实例化的效果就明显了。下面的例子

系统生成函数Say代码时,将使用到的上级变量如Name放入到一个自动生成的类型($Unnamed1)中,然后作为函数参数传递给Say函数,伪代码类似

using System;

以下为引用:

delegate void D();

type
$Unnamed1 = record
Name: string;
end;

class Test {
   static D[] F() {
       D[] result = new D[3];

procedure @1$SayHello$Say(var UnnamedParam: $Unnamed1);
begin
WriteLn(UnnamedParam.Name);
end;

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

procedure SayHello;
var
Name: string;
Unnamed1: $Unnamed1;
begin
Name := 'Flier Lu';

   static void Main() {
       foreach (D d in F()) d();
   }
}

Unnamed1.Name := Name;

的输出为:

Say(Unnamed1);
end;

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

   注意,根据判等操作(见3.7),由上面这个版本的F方法所建立的三个委托是相等的。另外,编译器可以(但不是必须)将这三个实例优化为一个单独的委托实例(见3.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

3.6 匿名方法求值
   运行时对匿名方法表达式的求值将得到一个委托实例,该委托实例引用了这个匿名方法和一组活动的外部变量的集合(可能为空)。当一个由匿名方法表达式求得的委托被调用时,匿名方法体将被执行。方法体中的代码可以使用由委托所引用的一组捕获了的外部变量。

   有匿名方法表达式得到的委托的调用链表包含一个唯一的入口点。委托的确切的目标对象和目标方法是未定义的。尤其是委托的目标对象是否为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); });
       ...
   }
}

   由于两个匿名方法委托具有相同的一组捕获的外部变量(为空),而且匿名方法在语义上是一致的,编译器可以令这两个委托引用一个相同的目标方法。甚至对于这两个匿名方法表达式,编译器可以返回完全一样的委托实例。

3.7 委托实例相等性
   下面的规则决定着匿名方法委托实例的判等操作符和Object.Equals方法的结果:

从语义上相同的带有相同一组(也可能是都没有没有)被捕获变量的匿名方法表达式(anonymous-method-expressions)所产生的委托实例可以(但不是必须)相等。
从语义上相同的单被捕获变量不同的的匿名方法表达式(anonymous-method-expressions)所产生的委托实例必须不相等。
3.8 明确赋值
   对匿名方法参数的明确赋值规则和命名方法(named method,区别于匿名方法)参数的明确复制规定一样。也就是说,引用参数和值参数必须同国明确的赋值进行初始化,而输出参数可以不比进行明确赋值。

   另外,如果要在匿名方法正常返回之前使用输出参数,则输出参数必须被明确赋值。

   当控制转移到匿名方法表达式的块中时,对于一个外部变量v的明确赋值规定与匿名方法表达式之前对v的明确赋值规定一样。

   对于一个匿名方法后面的变量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的明确赋值。

3.9 方法组转换
   和3.3节中描述的匿名方法隐式转换类似,从一个方法组到一个兼容的委托类型之间也存在一个隐式的转换。

   对于一个给定的方法组E和一个委托类型D,如果允许形为new D(E)的委托建立表达式(见2.9.6),则存在E到D的隐式转换,

   下面的例子:

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;

   方法组和匿名方法表达式会影响到重载抉择,但不会参与类型推断。更多细节参见2.6.4节

3.10 实现实例
   这一节将讨论一些标准C#构造中匿名方法的可能的实现。这里描述的实现和Visual 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");
   }
}

In the following example, the anonymous method references instance members of this:

   下面的例子中,你名方法引用了this的实例成员:

class Test {
   int x;

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

This can be translated to a compiler generated instance method containing the code of the anonymous method:

   它会被翻译为一个编译器生成的实例方法,该方法包含了匿名方法中的代码:

class Test {
   int x;

   void F() {
       D d = new D(__Method1);
   }

   void __Method1() {
       Console.WriteLine(x);
   }
}

In this example, the anonymous method captures a local variable:

   在这个例子中,匿名方法捕获了一个局部变量:

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

The lifetime of the local variable must now be extended to at least the lifetime of the anonymous method delegate. This can be achieved by “lifting” the local variable into a field of a compiler generated class. Instantiation of the local variable (§21.5.2) then corresponds to creating an instance of the compiler generated class, and accessing the local variable corresponds to accessing a field in the instance of the compiler generated class. Furthermore, the anonymous method becomes an instance method of the compiler generated class:

   局部变量的生存期现在必须扩展为至少持续到匿名方法委托的生存期结束。这可以通过将局部变量“提升”到一个编译器生成的类的域中来完成。局部变量的实例化(见3.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);
       }
   }
}

Finally, the following anonymous method captures this as well as two local variables with different lifetimes:

   最后,下面的匿名方法捕获了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); };
       }
   }
}

Here, a compiler generated class is created for each statement block in which locals are captured such that the locals in the different blocks can have independent lifetimes. An instance of __Locals2, the compiler generated class for the inner statement block, contains the local variable z and a field that references an instance of __Locals1. An instance of __Locals1, the compiler generated class for the outer statement block, contains the local variable y and a field that references this of the enclosing function member. With these data structures it is possible to reach all captured outer variables through an instance of __Local2, and the code of the anonymous method can thus be implemented as an instance method of that class.

   这里,编译器为每一个捕获了局部变量的语句块分别都生成了一个类。因此,那些在不同的块中所捕获的局部变量具有独立的生存期。编译器为内层语句块建立的类__Locals2的一个实例,包含了局部变量z和一个引用了__Locals1的实例的域。编译器为外层语句块建立的类__Locals1的一个实例,包含了局部变量y和一个引用了函数成员所在类的this的一个域。通过这些数据结构,可以通过__Locals2的一个实例来获得所有捕获的外部变量,而匿名方法中的代码因此被实现为该类的一个实例方法。

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);
       }
   }
}                  

本文由10bet手机官网发布于高并发,转载请注明出处:0语言规范匿名方法,中的多播委托笔记总结【10bet体育中文官网】

上一篇:DropdownList绑定的两种方法,下实现主从DropDownList互动的方法 下一篇:没有了
猜你喜欢
热门排行
精彩图文