简体   繁体   中英

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?

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 (ie 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 .

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

Create a quick test project to output the values and then look at them. It should answer the questions, and probably cause you to learn something extra in the process. (This is what most of the people who will answer your question have done.)

I think x and y in Foo object will be captured as value types. So the closure created for that lambda expression should not keep a reference to the Foo object. So the compiler may create a class for that closure as:

internal class CompilerGeneratedClassName
{
   private int x;
   private int y;
   public CompilerGeneratedClassName(int x, int y)
   {
     this.x = x;
     this.y = y;
   }

   public int CompilerGeneratedMethodName()
   {
     return this.y;
   }     
}

and

return new Bar(x, () => y); 

may be replaced by

return new Bar(x,new CompilerGeneratedClassName(x,y).CompilerGeneratedMethodName);

So I don't think that that there will be reference to the Foo object as a result of this closure. So Foo object could be GCed. I could be wrong. One thing that you can do is to write a small program, compile it, and inspect the generated IL in ILDASM tool.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM