引子
在.NET 1.x时我们可能会写这样的代码:1
public static void TestRequest(string url)
{
WebRequest request = HttpWebRequest.Create(url);
object[] context = new object[] { url, request };
request.BeginGetResponse(TestAsyncCallback, context);
}
public static void TestAsyncCallback(IAsyncResult ar)
{
object[] context = (object[])ar.AsyncState;
string url = (string)context[0];
WebRequest request = (WebRequest)context[1];
using (WebResponse response = request.EndGetResponse(ar))
{
Console.WriteLine("{0}: {1}", url, response.ContentLength);
}
}
我们需要自己维护一个object[]作为callback的参数,这样不仅是弱类型还很麻烦。好在.NET 2.x引入了匿名方法:
public static void TestRequest(string url)
{
WebRequest request = HttpWebRequest.Create(url);
request.BeginGetResponse(delegate(IAsyncResult ar)
{
using (WebResponse response = request.EndGetResponse(ar))
{
Console.WriteLine("{0}: {1}", url, response.ContentLength);
}
},
null);
}
很方便吧!2 新定义的delegate可以直接用TestRequest里的变量,就在于生成了一个闭包。
闭包(Closure)是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。3
C#中闭包的实现
从上边看来闭包与匿名方法有点关系,但.NET中本并没有什么匿名方法,我们还是用Reflector看下编译器到底生成了什么样的代码。这里注意要修改下Reflector的设置,View->Options->Disassembler->Optimization改为.Net 1.0 。
public static void TestRequest(string url)
{
<>c__DisplayClass1 CS$<>8__locals2 = new <>c__DisplayClass1();
CS$<>8__locals2.url = url;
CS$<>8__locals2.request = WebRequest.Create(CS$<>8__locals2.url);
CS$<>8__locals2.request.BeginGetResponse(new AsyncCallback(CS$<>8__locals2.<TestRequest>b__0), null);
}
[CompilerGenerated]
private sealed class <>c__DisplayClass1
{
// Fields
public WebRequest request;
public string url;
// Methods
public void b__0(IAsyncResult ar)
{
using (WebResponse response = this.request.EndGetResponse(ar))
{
Console.WriteLine("{0}: {1}", this.url, response.ContentLength);
}
}
}
可见编译器至少做了两件事
- 生成了一个类,包括匿名方法的方法体和所有引用的自由变量
- 调用匿名方法的方法里的相关变量都被替换为生成类的字段,相关方法被替换为生成类的方法
练习
考虑下边代码的输出
1、
int i = 0;
Action action = delegate() { Console.WriteLine(i); };
i++;
action();
2、
int i = 0;
Action action = delegate() { i++; };
action();
Console.WriteLine(i);
--------------------------------------------
1、参考上一节,相关变量都被替换为生成类的字段,修改 i 时也改掉了生成类中的 i ,所以输出 1 。
这也许不是我们想要的结果,其实resharper对这种写法会有一个access to modified closure的提示。
2、同样的道理,WriteLine的 i 被替换为生成类的字段,所以输出 1 。
-----------------------------------------------------------------------------
注1: 参考老赵从.NET中委托写法的演变谈开去(上):委托与匿名方法
注2: 当然.Net 3.5后有更方便的方法,但不是这里的关注点就不多提了
public static void TestRequest(string url)
{
WebRequest request = HttpWebRequest.Create(url);
request.BeginGetResponse(ar => TestAsyncCallback(ar, request, url), null);
}
注3:参考维基百科闭包 (计算机科学)
浙公网安备 33010602011771号