Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do lambdas scope to local variables?

So my understanding of how the compiler handles lambdas is limited.

My understanding is that the compiler takes your lambda and turns it into a real method.

If that's the case then how does it scope to local variables?

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

The above example will set the return value of ret to the caller which is a dynamic object of deserialized JSON.

How does that happen though if the compiler takes that completed event lambda and abstracts it into its own method? How does it know to set the ret value?

It's like me saying this (which obviously wont work)

        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());
        }
like image 747
Anthony Russell Avatar asked Dec 02 '22 19:12

Anthony Russell


2 Answers

It does that creating an anonymous class. For example consider this code:

int x = 0;

Action action = () => x = 2;

action();

Console.Write(x);

And the generated class :

enter image description here

IL code of the <Main>b__2 method which sets the value of 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'
like image 193
Selman Genç Avatar answered Dec 21 '22 10:12

Selman Genç


I recommend not focusing on how the compiler does such a thing as that is an implementation detail that can change over time and as others have said different compiler implementations (mono?). Instead, know that such a thing happens because of closure.

In Wiki:

In programming languages, a closure (also lexical closure or function closure) is a function or reference to a function together with a referencing environment—a table storing a reference to each of the non-local variables (also called free variables or upvalues) of that function.1 A closure—unlike a plain function pointer—allows a function to access those non-local variables even when invoked outside its immediate lexical scope.

like image 38
P.Brian.Mackey Avatar answered Dec 21 '22 10:12

P.Brian.Mackey