匿名方法

 http://www.microsoft.com/china/msdn/library/langtool/vcsharp/CreElegCodAnymMeth.mspx?mfr=true

匿名方法

C# 支持用于调用一个或多个方法的委托 (delegate)。委托提供运算符和方法来添加或删除目标方法,它也可以在整个 .NET 框架中广泛地用于事件、回调、异步调用、多线程等。然而,仅仅为了使用一个委托,有时您不得不创建一个类或方法。在这种情况下,不需要多个目标,并且调用的代码通常相对较短而且简单。在 C# 2.0 中,匿名方法是一个新功能,它允许定义一个由委托调用的匿名(也就是没有名称的)方法。

例如,下面是一个常规 SomeMethod 方法的定义和委托调用:

class SomeClass
{
   delegate void SomeDelegate();
   public void InvokeMethod()
   {
      SomeDelegate del = new SomeDelegate(SomeMethod);      
      del(); 
   }
   void SomeMethod()   
   {      
      MessageBox.Show("Hello");
   }
}

可以用一个匿名方法来定义和实现这个方法:

class SomeClass
{
   delegate void SomeDelegate();
   public void InvokeMethod()
   {
      SomeDelegate del = delegate() 
                         {
                             MessageBox.Show("Hello");
                         };
      del();   
   }
}

匿名方法被定义为内嵌 (in-line) 方法,而不是作为任何类的成员方法。此外,无法将方法属性应用到一个匿名方法,并且匿名方法也不能定义一般类型或添加一般约束。

您应该注意关于匿名方法的两件值得关注的事情:委托保留关键字的重载使用和委托指派。稍后,您将看到编译器如何实现一个匿名方法,而通过查看代码,您就会相当清楚地了解编译器必须推理所使用的委托的类型,实例化推理类型的新委托对象,将新的委托包装到匿名方法中,并将其指派给匿名方法定义中使用的委托(前面的示例中的 del)。

匿名方法可以用在任何需要使用委托类型的地方。您可以将匿名方法传递给任何方法,只要该方法接受适当的委托类型作为参数即可:

class SomeClass
{
   delegate void SomeDelegate();
   public void SomeMethod()   
   {      
      InvokeDelegate(delegate(){MessageBox.Show("Hello");});   
   }   
   void InvokeDelegate(SomeDelegate del)   
   {      
      del();   
   }
}

如果需要将一个匿名方法传递给一个接受抽象 Delegate 参数的方法,例如:

void InvokeDelegate(Delegate del);

则首先需要将匿名方法强制转换为特定的委托类型。

下面是一个将匿名方法作为参数传递的具体而实用的示例,它在没有显式定义 ThreadStart 委托或线程方法的情况下启动一个新的线程:

public class MyClass
{   
   public void LauchThread()
   {
      Thread workerThread = new Thread(delegate()
                            {
                                MessageBox.Show("Hello");
                            });      
      workerThread.Start();
   }
}

在前面的示例中,匿名方法被当作线程方法来使用,这会导致消息框从新线程中显示出来。

将参数传递到匿名方法

当定义带有参数的匿名方法时,应该在 delegate 关键字后面定义参数类型和名称,就好像它是一个常规方法一样。方法签名必须与它指派的委托的定义相匹配。当调用委托时,可以传递参数的值,与正常的委托调用完全一样:

class SomeClass
{
   delegate void SomeDelegate(string str);
   public void InvokeMethod()
   {
      SomeDelegate del = delegate(string str)
                         {
                             MessageBox.Show(str);
                         };
      del("Hello");   
   }
}

如果匿名方法没有参数,则可以在 delegate 关键字后面使用一对空括号:

class SomeClass
{
   delegate void SomeDelegate();
   public void InvokeMethod()
   {
      SomeDelegate del = delegate()
                         {
                             MessageBox.Show("Hello");
                         };
      del();   
   }
}

然而,如果您将 delegate 关键字与后面的空括号一起忽略,则您将定义一种特殊的匿名方法,它可以指派给具有任何签名的任何委托:

class SomeClass
{
   delegate void SomeDelegate(string str);
   public void InvokeMethod()
   {
      SomeDelegate del = delegate                         
                         {
                             MessageBox.Show("Hello");
                         };
      del("Parameter is ignored");   
   }
}

明显地,如果匿名方法并不依赖于任何参数,而且您想要使用这种与委托签名无关的方法代码,则您只能使用这样的语法。注意,当调用委托时,您仍然需要提供参数,因为编译器为从委托签名中推理的匿名方法生成无名参数,就好像您曾经编写了下面的代码(在 C# 伪码中)一样:

SomeDelegate del = delegate(string) 
                   {
                       MessageBox.Show("Hello");
                   };

此外,不带参数列表的匿名方法不能与指出参数的委托一起使用。

匿名方法可以使用任何类成员变量,并且它还可以使用定义在其包含方法范围之内的任何局部变量,就好像它是自己的局部变量一样。图 7 对此进行了展示。一旦知道如何为一个匿名方法传递参数,也就可以很容易地定义匿名事件处理,如图 8 所示。

因为 += 运算符仅仅将一个委托的内部调用列表与另一个委托的内部调用列表连接起来,所以可以使用 += 来添加一个匿名方法。注意,在匿名事件处理的情况下,不能使用 -= 运算符来删除事件处理方法,除非将匿名方法作为处理程序加入,要这样做,可以首先将匿名方法存储为一个委托,然后通过事件注册该委托。在这种情况下,可以将 -= 运算符与相同的委托一起使用,来取消将匿名方法作为处理程序进行注册。

匿名方法实现

编译器为匿名方法生成的代码很大程度上依赖于匿名方法使用的参数或变量的类型。例如,匿名方法使用其包含方法的局部变量(也叫做外部变量)还是使用类成员变量和方法参数?无论是哪一种情况,编译器都会生成不同的 MSIL。如果匿名方法不使用外部变量(也就是说,它只使用自己的参数或者类成员),则编译器会将一个私有方法添加到该类中,以便赋予方法一个唯一的名称。该方法的名称具有以下格式:

<return type> __AnonymousMethod$<random unique number>(<params>)

和其他编译器生成的成员一样,这都是会改变的,并且最有可能在最终版本发布之前改变。方法签名将成为它指派的委托的签名。

编译器只是简单地将匿名方法定义和赋值转换成推理委托类型的标准实例,以包装机器生成的私有方法:

SomeDelegate del = new SomeDelegate(__AnonymousMethod$00000000);

非常有趣的是,机器产生的私有方法并不显示在 IntelliSense 中,也不能显式地调用它,因为其名称中的美元符号对于 C# 方法来说是一个非法标记(但它是一个有效的 MSIL 标记)。

当匿名方法使用外部变量时,情况会更加困难。如果这样,编译器将用下面的格式添加具有唯一名称的私有嵌套类:

__LocalsDisplayClass$<random unique number>

嵌套类有一个名为 <this> 的指向包含类的引用,它是一个有效的 MSIL 成员变量名。嵌套类包含与匿名方法使用的每个外部变量对应的公共成员变量。编译器向嵌套类定义中添加一个具有唯一名称的公共方法,格式如下:

<return type> __AnonymousMethod$<random unique number>(<params>)

方法签名将成为被指派的委托的签名。编译器用代码替代匿名方法定义,此代码创建一个嵌套类的实例,并进行必要的从外部变量到该实例的成员变量的赋值。最后,编译器创建一个新的委托对象,以便包装嵌套类实例的公共方法,然后调用该委托来调用此方法。图 9 用 C# 伪代码展示了编译器为图 7 中定义的匿名方法生成的代码。

一般匿名方法

匿名方法可以使用一般参数类型,就像其他方法一样。它可以使用在类范围内定义的一般类型,例如:

class SomeClass<T>
{   
   delegate void SomeDelegate(T t);   
   public void InvokeMethod(T t)   
   {      
      SomeDelegate del = delegate(T item){...}    
      del(t);   
   }
}

因为委托可以定义一般参数,所以匿名方法可以使用在委托层定义的一般类型。可以指定用于方法签名的类型,在这种情况下,方法签名必须与其所指派的委托的特定类型相匹配:

class SomeClass
{   
   delegate void SomeDelegate<T>(T t);   
   public void InvokeMethod()   
   {      
      SomeDelegate<int> del = delegate(int number) 
                              { 
                                 MessageBox.Show(number.ToString()); 
                              };      
      del(3);   
   }
}

匿名方法示例

虽然乍一看匿名方法的使用可能像一种另类的编程技术,但是我发现它是相当有用的,因为在只要一个委托就足够的情况下,使用它就可以不必再创建一个简单方法。图 10 展示了一个有用的匿名方法的实际例子 — SafeLabel Windows 窗体控件。

Windows 窗体依赖于基本的 Win32 消息。因此,它继承了典型的 Windows 编程要求:只有创建窗口的线程可以处理它的消息。在 .NET 框架 2.0 中,调用错误的线程总会触发一个 Windows 窗体方面的异常。因此,当在另一个线程中调用窗体或控件时,必须将该调用封送到正确的所属线程中。Windows 窗体有内置的支持,可以用来摆脱这个困境,方法是用 Control 基类实现 ISynchronizeInvoke 接口,其定义如下:

public interface ISynchronizeInvoke 
{
   bool InvokeRequired {get;}
   IAsyncResult BeginInvoke(Delegate method,object[] args);
   object EndInvoke(IAsyncResult result);
   object Invoke(Delegate method,object[] args);
}

Invoke 方法接受针对所属线程中的方法的委托,并且将调用从正在调用的线程封送到该线程。因为您可能并不总是知道自己是否真的在正确的线程中执行,所以通过使用 InvokeRequired 属性,您可以进行查询,从而弄清楚是否需要调用 Invoke 方法。问题是,使用 ISynchronizeInvoke 将会大大增加编程模型的复杂性,因此较好的方法常常是将带有 ISynchronizeInvoke 接口的交互封装在控件或窗体中,它们会自动地按需使用 ISynchronizeInvoke。

例如,为了替代公开 Text 属性的 Label 控件,您可以定义从 Label 派生的 SafeLabel 控件,如图 10 所示。SafeLabel 重写了其基类的 Text 属性。在其 get 和 set 中,它检查 Invoke 是否是必需的。如果是这样,则它需要使用一个委托来访问此属性。该实现仅仅调用了基类属性的实现,不过是在正确的线程上。因为 SafeLabel 只定义这些方法,所以它们可以通过委托进行调用,它们是匿名方法很好的候选者。SafeLabel 传递这样的委托,以便将匿名方法作为其 Text 属性的安全实现包装到 Invoke 方法中。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值