tags:

views:

80

answers:

4

Hello,

The code below is taken directly from the sample project accompanying the article on MSDN introducing MVVM design pattern. I do not quite understand why the delegate sees the value of 'handler' other than null. My understanding was that the closure created for the delegate method contains all variables in scope that have been initialized up to this point in the execution, and since 'handler' is reassigned after the delegate is created the closure will contain 'handler' set to null.

Konstantin


EventHandler handler = null;
handler = delegate
{
    viewModel.RequestClose -= handler;
    window.Close();
};
viewModel.RequestClose += handler;
+3  A: 

The delegate captures the variable handler, not the contents of the variable.


It becomes more clear when you look at what the C# compiler compiles your code to, e.g., using Reflector. Your code is compiled roughly to this:

class MyAnonymousDelegate
{
    public ... viewModel;
    public ... window;
    public EventHandler handler;

    public void DoIt(object sender, EventArgs e)
    {
        this.viewModel.RequestClose -= this.handler;
        this.window.Close();
    }
}

var mad = new MyAnonymousDelegate();
mad.viewModel = viewModel;
mad.window = window;
mad.handler = null;

mad.handler = new EventHandler(mad.DoIt);

viewModel.RequestClose += mad.handler;
dtb
@dtb: yes your sample class definition clearly demonstrates that. assuming that it is indeed what the original code maps to roughly. it is just that as i said in my comment to another response, this is different from the semantics of a closure as seen in other languages, for example in functional languages where i can get a function variable with all the environment that it has seen when it was created without worrying about someone changing this environment from under it. or am i totally off?
akonsu
@akonsu: In functional languages, variables are usually not variable at all, i.e. the same variable can not have different values at different points of time. So there is no real difference between capturing a variable and the value of a variable in a functional language, because the variable has the same value all the time anyway. In C#, the value of the variable *can* change over time, so the language designers took the decision to capture the variable instead of the value. This is a bit surprising in some cases, but allows for greater flexibility.
dtb
ok, i understand, but if it captures just variables and not their values why does csc complain when it sees EventHandler handler = delegate {...}? :)
akonsu
@akonsu: Because `handler` can't be used before it has been assigned, which is only the case after the delegate has been created that attempts to use `handler`. So you first need to create the variable, then create the anonymous delegate which captures the variable, and then assign the delegate to the variable.
dtb
thank you. i understand what the error means. i guess this is a rhetorical question, so please ignore this if you do not feel like getting in to this fruitless discussion, but to me it seems that c# designers overlooked something: on one hand they do not capture values, just variables, on the other hand they complain if these variables are unassigned when they are captured...
akonsu
@akonsu: Actually, I'm wrong. The reason is that you can't read from or assign to a variable before it has been declared. In `var x = expression;` the expression is technically evaluated before `x` is declared. So you can't use `x` in the expression. (And you can only assign to, but not read from an unassigned variable. But that's unrelated here.)
dtb
+1  A: 

A closure doesn't copy the variables, it maintains a reference. When the delegate is created and the handler is set, this change is updated.

DeadMG
thanks, i did not know about this... this is different from, say, ML. to me it seems like a deviation from the definition of a closure...
akonsu
No. Closures hold and keep their own variables, not just values.
DeadMG
+1  A: 

The handler is initialised before asigning:

       static void Main(string[] args)
    {

        EventHandler handler = null;

        handler = delegate
        {
            AppDomain.CurrentDomain.ProcessExit -= handler;

        };
        AppDomain.CurrentDomain.ProcessExit += handler;
    }

Compiles to:

.method private hidebysig static void  Main(string[] args) cil managed
{
   .entrypoint
   // Code size       51 (0x33)
   .maxstack  4
   .locals init ([0] class ConsoleApplication1.Program/'c__DisplayClass1'    'CS$8__locals2')
   IL_0000:  newobj     instance void    ConsoleApplication1.Program/'c__DisplayClass1'::.ctor()
   IL_0005:  stloc.0
  IL_0006:  nop
  IL_0007:  ldloc.0
  IL_0008:  ldnull
  IL_0009:  stfld      class [mscorlib]System.EventHandler ConsoleApplication1.Program/'c__DisplayClass1'::'handler'
  IL_000e:  ldloc.0
  IL_000f:  ldloc.0
  IL_0010:  ldftn      instance void ConsoleApplication1.Program/'c__DisplayClass1'::'b__0'(object,
                                                                                                    class [mscorlib]System.EventArgs)
  IL_0016:  newobj     instance void [mscorlib]System.EventHandler::.ctor(object,
                                                                          native int)
  IL_001b:  stfld      class [mscorlib]System.EventHandler ConsoleApplication1.Program/'c__DisplayClass1'::'handler'
  IL_0020:  call       class [mscorlib]System.AppDomain [mscorlib]System.AppDomain::get_CurrentDomain()
  IL_0025:  ldloc.0
  IL_0026:  ldfld      class [mscorlib]System.EventHandler ConsoleApplication1.Program/'c__DisplayClass1'::'handler'
  IL_002b:  callvirt   instance void [mscorlib]System.AppDomain::add_ProcessExit(class [mscorlib]System.EventHandler)
  IL_0030:  nop
  IL_0031:  nop
  IL_0032:  ret
} // end of method Program::Main
Aliostad
+2  A: 

Write it like this:

EventHandler handler = delegate
{
    viewModel.RequestClose -= handler;
    window.Close();
};
viewModel.RequestClose += handler;

To get error CS0165: Use of unassigned local variable 'handler'.

Questionable diagnostic, it isn't actually unassigned. The hidden class that implements the anonymous method does in fact get created before the captured value of 'handler' is assigned. That cannot be easy to implement in the C# compiler though. Edge case.

Hans Passant