CSDN博客

img tlping

C# 2.0 Specification(匿名方法)(一)

发表于2004/3/5 13:36:00  794人阅读

21匿名方法

21.1.匿名方法表达式

匿名方法表达式(anonymous-method-expression)定义了匿名方法(anonymous method),它将计算为引用该方法的一个具体值。

l         primary-no-array-creation-expression(基本非数组创建表达式:)

anonymous-method-expression
(匿名方法表达式)

l         anonymous-method-expression:
delegate   anonymous-method-signature opt   block
(匿名方法表达式: delegate 匿名方法签名 可选  块)

l         anonymous-method-signature:
(   anonymous-method-parameter-list opt   )
(匿名方法签名: 匿名方法参数列表 可选)

l         anonymous-method-parameter-list:
anonymous-method-parameter
anonymous-method-parameter-list   ,   anonymous-method-parameter
(匿名方法参数列表: 匿名方法参数 匿名方法参数列表)

l         anonymous-method-parameter:
parameter-modifieropt   type   identifier
(匿名方法参数: 参数修饰符 可选 类型 标识符)

匿名方法表达式被归类为具有特定转换规则21.3)的值。

匿名方法表达式为参数、局部变量和常数定义了一个新的声明空间,并且为标签(§3.3)定义了一个新的声明空间。

 

21.2匿名方法签名

可选的匿名方法签名(anonymous-method-signature)为该匿名方法定义了正式参数的名字和类型。匿名方法的参数作用域为块(block)。匹配其作用域包含匿名方法表达式(anonymous-method-expression)的局部变量、局部常数或参数的名字,对于匿名方法参数的名字来说是一个编译时错误。

如果一个匿名方法表达式具有匿名方法签名,那么与之兼容的委托类型将被限制为那些具有相同顺序21.3)相同参数类型和修饰符的委托类型集合。如果匿名方法表达式不具有匿名方法签名,那么与之兼容的委托类型将被限制为那些没有输出参数的委托类型集合。

 

请注意,匿名方法签名不能包含特性或者参数数组。不过,匿名方法签名可以与其参数列表包含参数数组的委托类型兼容。


21.3匿名方法转换

匿名方法表达式被归类为一个无类型的值。匿名方法表达式可以用于委托创建表达式21.3.1)中。匿名方法表达式的所有其他合法的使用取决于在此定义的隐式转换。

隐式转换存在来自于与任何委托兼容的匿名方法表达式。如果D是一个委托类型,而A是一个匿名方法表达式,那么如果下面的条件满足的话,D就与A兼容:

l         首先,D的参数类型与A兼容:

n         如果A不包含匿名方法签名,那么D可以有零或多个任意类型的参数,前提是D没有任何参数具有输出参数修饰符。

n         如果A具有匿名方法签名,那么D必须具有相同数量的参数,A的每个参数与D的对应参数必须具有相同的类型,并且在A上的每个参数的refout修饰符的存在与否,都必须与D的对应参数相匹配。D的最后一个参数是否是参数数组和DA的兼容性无关。

l         其次,D的返回类型必须与A兼容,对于这些规则,不考虑A包含任何其他匿名方法块的情况。

n         如果D采用void声明返回类型,那么包含在A中的任何返回语句都不应该指定表达式。

n         如果D采用类型R声明返回类型,那么那么包含在A中的任何返回语句的都必须指定一个可以隐式转换(§6.1)R的表达式。并且,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具有输出参数
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)]可被用作将匿名方法转换到一个委托类型的替代语法。如果用作委托创建表达式的实参的表达式是一个匿名方法表达式,那么匿名方法将使用上面定义的隐式转换规则转换到给定的委托类型。例如,如果D是一个委托类型,那么表达式

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

等价于

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

 

21.4匿名方法块

匿名方法表达式的块遵循下列规则:

l         如果匿名方法包含签名,那么在签名中指定的参数在块内是有效的。如果匿名方法不具有签名,它可以被转换为具有参数的委托类型(§21.3),但参数在块内不可访问。

l         除了在最接近的封闭匿名方法签名中指定的refout参数(如果有的话)以外,对于块来说访问ref或者out参数将导致编译时错误。

l         this的类型是一个结构类型时,对于块来说,访问this将导致编译时错误。无论该访问是显式的(像this.x)或者隐式的(像对于在结构实例的成员中的x),情况都是如此。该规则只是禁止此类访问方式,但并不影响在结构中成员查找的结果。

l         块可以访问匿名方法的外部变量21.5)。当匿名方法表达式被计算21.6)的时候,对于外部变量的访问,将会引用激活的(active)变量的实例。

l         对于块来说,包含一个其目标在块之外,或一个内嵌的匿名方法的块之内的goto语句、break语句或continue语句,将导致编译时错误。

l         在块内的return 语句,将从最接近的封闭匿名方法调用中返回控制权,而不是从封闭函数成员中返回。在return 语句中指定的表达式必须与某个委托类型兼容,而最接近的匿名方法表达式将被转换到该委托类型21.3)


执行一个匿名方法的程序块,除了通过匿名方法表达式的计算和调用(evaluation and invocation)之外,是否还有其他方法,并没有明确地详细说明。特别的是,编译器可以通过合成一个或多个命名方法或类型来实现匿名方法,任何此类合成的元素的名字,必须为编译器的使用而保留在一个地方:名字必须保留两个连续下划字符。

21.5外部变量

作用域包含匿名方法表达式的任何局部变量、值参数和参数数组,都被称为匿名方法表达式的外部变量。在类的实例函数成员中,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被匿名方法所捕获,并且x的生存期至少被延长,直到从F中返回的委托可以被垃圾回收为止(在这里,这一点直到程序结束才满足),既然匿名方法的每次调用都在x的相同实例上进行操作,该示例输出的结果为:

1
2
3

当局部变量或值参数被匿名方法所捕获时,该局部变量和值参数将不再被认为是固定的(fixed)变量(§18.3),相反它成了可移动的(movable)变量。因此,任何取得被捕获的外部变量地址的不安全代码都必须首先使用fixed语句固定该变量。

21.5.2局部变量实例化

当程序执行到变量的作用域时,局部变量就被认为是实例化(instantiated)了。例如,当下面的方法被调用时,局部变量将被三次实例化和初始化——对于循环中的每次迭代都有一次。

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

通常,我们无法确切地看到一个局部变量多久被实例化一次——因为实例化的生命期被拆散(disjoint)了,可能的情况是,每次实例化都只是使用相同的存储位置。然而当一个匿名方法捕获一个局部变量的时候,实例化的影响将变得很明显。如示例

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] =
del
egate { 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匿名方法计算

匿名方法表达试的运行时计算产生一个引用匿名方法的委托实例,并且被捕获的外部变量的集合(可能为空)在计算时(the time of the evaluation)是活跃的(active)。当由匿名方法表达式所产生的委托被调用时,匿名方法体就会执行。方法体内的代码将使用由该委托引用而被捕获的外部变量执行。

由匿名方法表达时产生的委托调用列表包含一个单一入口。该委托的确切目标对象和目标方法都是未指定的。需要特别的注意的是,委托的目标对象是否为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); });
      ...
  }
}

由于两个匿名方法委托具有被捕获外部变量的相同集合(都为空),并且由于匿名方法在语义上是相同的,所以允许编译器产生引用同一目标方法的委托。实际上,这里允许编译器从两个匿名方法表达式返回相同的委托实例。

(to be continued)

阅读全文
0 0

相关文章推荐

img
取 消
img