C# lambdas如何作用于局部变量?

C# lambdas如何作用于局部变量?,c#,compiler-construction,lambda,C#,Compiler Construction,Lambda,因此,我对编译器如何处理lambda的理解是有限的 我的理解是,编译器接受lambda并将其转换为实际方法 如果是这样的话,那么它是如何作用于局部变量的呢 public async Task<dynamic> GetWebStuff() { dynamic ret = ""; WebClient wc = new WebClient(); wc.DownloadStringCompleted +=

因此,我对编译器如何处理lambda的理解是有限的

我的理解是,编译器接受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;
    }
公共异步任务GetWebStuff() { 动态ret=“”; WebClient wc=新的WebClient(); wc.DownloadStringCompleted+=异步(s,a)=> { ret=wait Newtonsoft.Json.JsonConvert.DeserializeObject(a.Result.ToString()); }; wc.DownloadString(“http://www.MyJson.com"); 返回ret; } 上面的示例将ret的返回值设置为调用方,调用方是反序列化JSON的动态对象

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

就像我说的那样(这显然行不通)

公共异步任务GetWebStuff() { 动态ret=“”; WebClient wc=新的WebClient(); wc.DownloadStringCompleted+=wc_DownloadStringCompleted; 下载字符串(“google.com”); 返回ret; } void wc_DownloadStringCompleted(对象发送方,DownloadStringCompletedEventArgs e) { ret=wait Newtonsoft.Json.JsonConvert.DeserializeObject(e.Result.ToString()); }
我建议不要把重点放在编译器是如何做的,因为这是一个随时间变化的实现细节,正如其他人所说的不同的编译器实现(?)。相反,要知道这样的事情发生的原因是

在Wiki中:

在编程语言中,闭包(也称为词法闭包或函数) 闭包)是函数或对函数的引用以及 引用环境—存储对每个环境的引用的表 其中的非局部变量(也称为自由变量或upvalues) 功能。闭包不同于普通函数指针,它允许 函数访问这些非局部变量,即使在外部调用时也是如此 它的直接词汇范围


它可以创建一个匿名类。例如,考虑这个代码:

int x = 0;

Action action = () => x = 2;

action();

Console.Write(x);
以及生成的类:

设置
x
值的
b_uu2
方法的IL代码:

    .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'
.method程序集隐藏实例void
“b__2”(cil管理)
{
//代码大小10(0xa)
.maxstack 8
IL_0000:ldarg.0
IL_0001:ldc.i4.2
IL_0002:stfld int32控制台应用程序1.Program/'c__DisplayClass0':x
IL_0007:br.s IL_0009
IL_0009:ret
}//方法“c_uuDisplayClass0”的结尾:“b_uu2”
首先,您的代码不起作用。由于与你的问题完全无关的原因,它不起作用,但它仍然不起作用

当下载实际完成时,代码确实成功地将
ret
变异为下载值的结果。遗憾的是,在这之前很久,您就已经返回了一个值,因为您没有等待下载完成就返回了任务的结果

您会注意到,
getwebstaff
的实现实际上会生成一个编译器警告,指出“此异步方法缺少'await'运算符,将同步运行[…]”。每当您看到此警告时,您几乎可以肯定您的方法设计不正确,因为它看起来是异步的,但实际上并没有异步执行任何操作

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

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;
}
public任务getwebstaff()
{
var tcs=new TaskCompletionSource();
WebClient wc=新的WebClient();
wc.DownloadStringCompleted+=异步(s,a)=>
{
tcs.TrySetResult(等待Newtonsoft.Json.JsonConvert.DeserializeObject(
a、 Result.ToString());
};
wc.DownloadStringAsync(新Uri(“http://www.MyJson.com"));
返回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;
}
类closure类
{
公共任务完成源tcs;
公共异步任务匿名方法1(对象s,
下载DataCompletedEventArgs(a)
{
tcs.TrySetResult(等待Newtonsoft.Json.JsonConvert.DeserializeObject(
a、 Result.ToString());
}
}
公共任务GetWebStuff()
{
ClosureClass closure=新的ClosureClass();
closure.tcs=新任务完成源();
WebClient wc=新的WebClient();
wc.DownloadStringCompleted+=closure.AnonymousMethod1;
wc.DownloadStringAsync(新Uri(“http://www.MyJson.com"));
返回closure.tcs.Task;
}

关闭变量时,将创建一个新的闭包类,每个关闭变量都有一个实例变量。lambda被转换为使用这些实例字段的实例方法。让lambda创建新类型实例的方法,在使用封闭局部变量的地方使用它的字段而不是局部变量,然后在lambda所在的地方使用新的命名方法。

lol很抱歉,但我永远不能接受这个答案。如果每个人都接受事情就是这样,从不问为什么,那么我们就永远不会有创新。我想如果你理解闭包,你就不会这么说。@P.Brian.Mackey没有看到链接。那就更好了哈哈,谢谢。所以,理论上,不同平台上不同的C#实现可以以不同的方式处理规范。基本上,有人可以用lisp实现c语言。在这种情况下,拉姆达斯将是一等公民。如果您对.NET编译器如何处理lambdas感兴趣,我认为应该在问题中明确说明
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;
}