Captured Arguments for Anonymous Methods are not [Serializable]

Here's one for the "Huh…I didn't know that…although now that I do, it's probably not that important"-department.
 
When you let the C# 2.0 compiler capture variables for use within the body of an anonymous method you're passing to someone, the class that the compiler synthesizes for holding & passing the captured arguments to the target method is not marked [Serializable]. For example, consider the following program:
 
using System;
 
class Program
{
    static void Main()
    {
        AppDomain ad2 = AppDomain.CreateDomain("Math Domain");
 
        ad2.DoCallBack(CallDoMath);                 // Okay.
        ad2.DoCallBack(delegate { DoMath(2, 2); }); // Okay.
 
        int x = 3;
        ad2.DoCallBack(delegate { DoMath(x, x); }); // SerializationException.
    }
 
    static void CallDoMath()
    {
        DoMath(1, 1);
    }
 
    static void DoMath(int a, int b)
    {
        Console.WriteLine(
            "[{0}] {1} + {2} = {3}",
            AppDomain.CurrentDomain.FriendlyName,
            a, b, a + b
        );
    }
}
 
At (1), a request is made to invoke the CallDoMath method in a different AppDomain than the one Main is executing in. AppDomain.DoCallback expects a reference to a CrossAppDomainDelegate. Since the signature of CallDoMath matches the signature of CrossAppDomainDelegate, the compiler is happy to expand ad2.DoCallBack(CallDoMath) into ad2.DoCallback(new CrossAppDomainCallback(CallDoMath)) for me. The result is that CallDoMath executes in the context of the AppDomain named "Math Domain".
 
At (2), an anonymous delegate is used to call DoMath(2, 2) "directly". In typical anonymous delegate fashion, this approach just saved me from having to write a stub like CallDoMath that conforms to the signature of CrossAppDomainDelegate. Instead, I've let the compiler write that little method for me. The results are the same as for (1).
 
But at (3), I've reference a local variable named 'x' within the body of the anonymous method that will call DoMath(x, x). That variable is a local variable located on the callstack for the Main method. In this situation, the compiler conspires to 'capture' that variable into an object on the heap, the reference to which is passed to the compiler-synthesized anonymous method. So even though it looks like I'm referencing the variable 'x' directly from within the anonymous method, I'm really dereferencing a field of an object. This line of code results in a SerializationException because (a) it's a cross-AppDomain call, which requires that all parameters be either [Serializable] or extend MarshalByRefObject and (b) the compiler-synthesized class that holds the captured variables is neither.
 
In retrospect, this is easy enough to see using ILDASM or Reflector.  But even though I've looked at the compiler-synthesized class for captured arguments before, it's not something I ever paid attention to until I bumped into it a few minutes ago tinkering with some multi-AppDomain code.
 
At any rate, there's a bit of trivia for the archives…

Posted Mar 24 2006, 04:12 PM by mike-woodring

Comments

Javier Lozano wrote re: Captured Arguments for Anonymous Methods are not [Serializable]
on 03-27-2006 5:23 PM
Pretty interesting find. It makes plenty of sense, though. The compiler creates the class under the current application domain since that's what's executing the code.

Thanks for the post!
anonymous e mail reflector wrote anonymous e mail reflector
on 07-07-2008 12:45 AM

Pingback from  anonymous e mail reflector

Add a Comment

(required)  
(optional)
(required)  
Remember Me?