lambdas如何定位到局部变量?

时间:2014-05-27 18:39:36

标签: c# compiler-construction lambda

所以我对编译器如何处理lambdas的理解是有限的。

我的理解是编译器将你的lambda转换成一个真正的方法。

如果是这样,那么它如何适用于局部变量?

    public async Task<dynamic> GetWebStuff()
    {
        dynamic ret = "";

        WebClient wc = new WebClient();          

        wc.DownloadStringCompleted += async (s, a) => 
        {
            ret = await Newtonsoft.Json.JsonConvert.DeserializeObject(a.Result.ToString());
        };

        wc.DownloadString("http://www.MyJson.com");

        return ret;
    }

上面的示例将ret的返回值设置为调用者,调用者是反序列化JSON的动态对象。

如果编译器接受完成的事件lambda并将其抽象为自己的方法,那怎么会发生?如何知道设置ret值?

就像我说的那样(显然不会起作用)

        public async Task<dynamic> GetWebStuff()
        {
            dynamic ret = "";

            WebClient wc = new WebClient();

            wc.DownloadStringCompleted += wc_DownloadStringCompleted;            

            wc.DownloadString("google.com");

            return ret;
        }

        void wc_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
        {
            ret = await Newtonsoft.Json.JsonConvert.DeserializeObject(e.Result.ToString());
        }

3 个答案:

答案 0 :(得分:9)

它创建了一个匿名类。例如,请考虑以下代码:

int x = 0;

Action action = () => x = 2;

action();

Console.Write(x);

生成的类:

enter image description here

<Main>b__2方法的IL代码,用于设置x

的值
    .method assembly hidebysig instance void 
        '<Main>b__2'() cil managed
{
  // Code size       10 (0xa)
  .maxstack  8
  IL_0000:  ldarg.0
  IL_0001:  ldc.i4.2
  IL_0002:  stfld      int32 ConsoleApplication1.Program/'<>c__DisplayClass0'::x
  IL_0007:  br.s       IL_0009
  IL_0009:  ret
} // end of method '<>c__DisplayClass0'::'<Main>b__2'

答案 1 :(得分:5)

我建议不要关注编译器如何做这样的事情,因为这是一个可以随时间变化的实现细节,而其他人已经说过不同的编译器实现(mono?)。相反,要知道因closure而发生这样的事情。

在维基:

  

在编程语言中,闭包(也是词法闭包或函数   closure)是一个函数或函数的引用   引用环境 - 存储对每个引用的引用的表   非局部变量(也称为自由变量或上变量)   function。1一个闭包 - 不像一个普通的函数指针 - 允许一个   函数来访问那些非局部变量,即使在外部调用时也是如此   它的直接词汇范围。

答案 2 :(得分:3)

首先,您的代码无法正常工作。由于与您的问题完全无关的原因,它不起作用,但它仍然无法正常工作。

当下载实际完成时,代码确实成功地将ret变为下载值的结果。遗憾的是,在此之前您已经返回了值 long ,因为您返回任务的结果而不等待下载完成。

您会注意到GetWebStuff的实施实际上会生成编译器警告,指出&#34;此异步方法缺少等待&#39;操作员将同步运行[...]&#34;。每当您看到此警告时,您几乎可以确定您的方法设计不正确,因为它出现是异步的,而实际上并不是异步执行任何操作。

以下是您的方法的有效实现:

public Task<dynamic> GetWebStuff()
{
    var tcs = new TaskCompletionSource<dynamic>();

    WebClient wc = new WebClient();

    wc.DownloadStringCompleted += async (s, a) =>
    {
        tcs.TrySetResult(await Newtonsoft.Json.JsonConvert.DeserializeObject(
            a.Result.ToString()));
    };

    wc.DownloadStringAsync(new Uri("http://www.MyJson.com"));

    return tcs.Task;
}

此方法返回一个任务,该任务在事件被触发之前不会被完成,此时任务的结果将被设置为下载的结果。

关于闭包的工作原理,最简单的方法就是简单地看看编译器将这段代码转换成什么:

class ClosureClass
{
    public TaskCompletionSource<dynamic> tcs;
    public async Task AnonymousMethod1(object s, 
        DownloadDataCompletedEventArgs a)
    {
        tcs.TrySetResult(await Newtonsoft.Json.JsonConvert.DeserializeObject(
            a.Result.ToString()));
    }
}

public Task<dynamic> GetWebStuff()
{
    ClosureClass closure = new ClosureClass();
    closure.tcs = new TaskCompletionSource<dynamic>();

    WebClient wc = new WebClient();

    wc.DownloadStringCompleted += closure.AnonymousMethod1;

    wc.DownloadStringAsync(new Uri("http://www.MyJson.com"));

    return closure.tcs.Task;
}

当关闭变量时,会创建一个新的闭包类,每个闭合的变量都有一个实例变量。 lambda被转换为使用这些实例字段的实例方法。具有lambda的方法创建该新类型的实例,使用其字段而不是使用本地关闭的局部因素,然后使用lambda所在的新命名方法。