views:

221

answers:

4

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?

A: 

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.)

John Fisher
I'm also curious about the IL and the theory of it. I have written tests to answer some of my questions, but I'd like to understand more what is actually going on. I thought some super smart people on SO could shed some more light.
Stefan Moser
You could use Reflector to see the IL produced by your test project, but of course an expert's comments would be more immediately understandable.
John Fisher
+1  A: 

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

CSharpAtl
You're right, in my example, Foo also needs to be marked as Serializable. So this means that it's serializing all of Foo.
Stefan Moser
@Stefan : no, none of your classes needs the Serializable attribute (at least not for XML serialization). This attribute is for serialization with formatters (BinaryFormatter, SoapFormatter...)
Thomas Levesque
@Thomas Right, I'm using the BinaryFormatter.
Stefan Moser
A: 

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.

Mehmet Aras
I have inspected the IL, but I'm not all that familiar with IL and can't figure out anything special that the compiler has done.
Stefan Moser
The compiler does not need a closure class for this; it is a field, not a local method variable. It is the `this` that is passed. See my answer for more.
Marc Gravell
Thank you Marc.
Mehmet Aras
+4  A: 

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.

Marc Gravell
In section 6.5.3 of the C# Programming Language (3rd edition), there is an example very similar to this case and it is handled just as Marc explained by a compiler generated instance method on Foo.
Mehmet Aras
Fantastic answer Marc! Thanks!
Stefan Moser