Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Anonymous methods, scope, and serialization

Let's say I have the following code:

public class Foo
{
    private int x;
    private int y;

    public Bar CreateBar()
    {
        return new Bar(x, () => y);
    }
}

[Serializable]
public class Bar
{
    private int a;
    private Func<int> b;

    public Bar(int a, Func<int> b)
    {
        this.a = a;
        this.b = b;
    }
}

What happens with the scope of the objects and values in this scenario? Since x is a value type, it is passed to Bar by value, and therefore, nothing needs to happen to its scope. But what happens to y? The value for y needs to stick around to be returned when b is actually evaluated. Is all of Foo kept around to evaluate y at a later time? I can only assume that Foo is not GC'ed.

Now let's say that we serialize Bar to disk, then deserialize it later. What has actually been serialized? Did it serialze Foo as well? What magic has gone on so that b can be evaluated after Bar has been deserialized? Can you explain what is happening in the IL?

like image 699
Stefan Moser Avatar asked Aug 14 '09 17:08

Stefan Moser


2 Answers

Update: to see what is actually happening without having to resort to IL: Using reflector to understand anonymous methods and captured variables


When you use:

public Bar CreateBar()
{
    return new Bar(x, () => y);
}

You are implicitly meaning this.y; so in terms of the delegate, it is the reference to Foo that is included. As such, the instance of Bar (via the delegate) keeps the entire Foo alive (not garbage-collected) until the Bar is available for collection.

In particular, there is no need (in this case) for the compiler to generate an additional class to handle captured variables; the only thing required is the Foo instance, so a method can be generated on Foo. This would be be more complex if the delegate involved local variables (other than this).

In terms of serialization... well, the first thing I'd say is that serializing delegates is a very very bad idea. However, BinaryFormatter will walk delegates, and you can (in theory) end up with a serialized Bar, a serialized Foo, and a serialized delegate to link them - but only if you mark Foo as [Serializable].

But I stress - this is a bad idea. I rarely use BinaryFormatter (for a variety of reasons), but a common question I see by people using it is "why is it trying to serialize (some random type)". Usually, the answer is "you are publishing an event, and it is trying to serialize the subscriber", in which case the most common fix would be to mark the event's field as [NonSerialized].


Rather than looking at IL; another way to investigate this is to use reflector in .NET 1.0 mode (i.e. without it swapping in anonymous methods); then you can see:

public Bar CreateBar()
{
    return new Bar(this.x, new Func<int>(this.<CreateBar>b__0));
}
[CompilerGenerated]
private int <CreateBar>b__0()
{
    return this.y;
}

As you can see; the thing passed to Bar is a delegate to a hidden method (called <CreateBar>b__0()) on the current instance (this). So it is the instance to the current Foo that is passed to the Bar.

like image 168
Marc Gravell Avatar answered Oct 18 '22 01:10

Marc Gravell


I got error when trying to serialize when it was reflecting the object to serialize.

my example:

[Serializable]
    public class SerializeTest
    {
        //public SerializeTest(int a, Func<int> b)
        //{
        //    this.a = a;
        //    this.b = b;
        //}

        public SerializeTest()
        {

        }

        public int A 
        {
            get
            {
                return a;
            }

            set
            {
                a = value;
            }
        }
        public Func<int> B 
        {
            get
            {
                return b;
            }
            set
            {
                b = value;
            }
        }


        #region properties

        private int a;
        private Func<int> b;



        #endregion

        //serialize itself
        public string Serialize()
        {
            MemoryStream memoryStream = new MemoryStream();

            XmlSerializer xs = new XmlSerializer(typeof(SerializeTest));
            using (StreamWriter xmlTextWriter = new StreamWriter(memoryStream))
            {
                xs.Serialize(xmlTextWriter, this);
                xmlTextWriter.Flush();
                //xmlTextWriter.Close();
                memoryStream = (MemoryStream)xmlTextWriter.BaseStream;
                memoryStream.Seek(0, SeekOrigin.Begin);
                StreamReader reader = new StreamReader(memoryStream);

                return reader.ReadToEnd();
            }
        }

        //deserialize into itself
        public void Deserialize(string xmlString)
        {
            String XmlizedString = null;

            using (MemoryStream memoryStream = new MemoryStream())
            {
                using (StreamWriter w = new StreamWriter(memoryStream))
                {
                    w.Write(xmlString);
                    w.Flush();

                    XmlSerializer xs = new XmlSerializer(typeof(SerializeTest));
                    memoryStream.Seek(0, SeekOrigin.Begin);
                    XmlReader reader = XmlReader.Create(memoryStream);

                    SerializeTest currentConfig = (SerializeTest)xs.Deserialize(reader);

                    this.a = currentConfig.a;
                    this.b = currentConfig.b;

                    w.Close();
                }
            }
        }

    }

class Program
    {
        static void Main(string[] args)
        {

            SerializeTest test = new SerializeTest() { A = 5, B = ()=>67};
            string serializedString =  test.Serialize();


}
}

Here is a link to doing it...little more complex: Serializing Anon Delegates

like image 22
CSharpAtl Avatar answered Oct 18 '22 02:10

CSharpAtl