匿名方法,范围和序列化

时间:2009-08-14 17:27:26

标签: c# delegates scope anonymous-methods

假设我有以下代码:

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;
    }
}

此方案中对象和值的范围会发生什么?由于x是值类型,因此它将按值传递给Bar,因此,不需要对其范围进行任何操作。但是y会发生什么?当实际评估b时,y的值需要保持不变。是不是所有的Foo都会在以后评估y?我只能假设Foo不是GC。

现在假设我们将Bar序列化为磁盘,然后再对其进行反序列化。什么实际上被序列化?它是否也将Foo序列化?是什么魔法发生了以便在反序列化后可以评估b?你能解释IL中发生的事情吗?

4 个答案:

答案 0 :(得分:5)

更新:看看实际发生的事情,而不必诉诸IL:Using reflector to understand anonymous methods and captured variables


使用时:

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

你隐含地意味着this.y;因此,就代表而言,包括的{strong>引用至Foo。因此,Bar的实例(通过委托)使整个Foo保持活动状态(不是垃圾收集),直到Bar可用于收集。

特别是,编译器不需要(在这种情况下)生成一个额外的类来处理捕获的变量;唯一需要的是Foo实例,因此可以在Foo上生成方法。如果委托涉及局部变量(this除外),那将会更复杂。

就序列化而言......好吧,首先我要说的是序列化代表是一个非常糟糕的主意。但是,BinaryFormatter 引导代理,您可以(理论上)最终获得序列化的Bar,序列化的Foo和序列化的委托链接他们 - 如果您将Foo标记为[Serializable],则

但我强调 - 这是一个坏主意。我很少使用BinaryFormatter(由于各种原因),但是人们使用它时遇到的一个常见问题是“为什么要尝试序列化(某些随机类型)”。通常,答案是“您正在发布一个事件,它正在尝试序列化订阅者”,在这种情况下,最常见的修复方法是将事件的字段标记为[NonSerialized]


而不是看IL;另一种研究方法是在.NET 1.0模式下使用反射器(即不用匿名方法交换);然后你可以看到:

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

正如你所看到的;传递给Bar的东西是当前实例(<CreateBar>b__0())上隐藏方法(名为this)的委托。因此 是传递给Foo的当前Bar的实例。

答案 1 :(得分:1)

在反映要序列化的对象时尝试序列化时遇到错误。

我的例子:

[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();


}
}

以下是执行此操作的链接......稍微复杂一点:Serializing Anon Delegates

答案 2 :(得分:0)

创建一个快速测试项目以输出值,然后查看它们。它应该回答问题,并可能会让你在这个过程中学到更多东西。 (这就是大多数将回答你问题的人所做的事。)

答案 3 :(得分:0)

我认为Foo对象中的x和y将被捕获为值类型。因此,为该lambda表达式创建的闭包不应该保持对Foo对象的引用。因此编译器可以为该闭包创建一个类:

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;
   }     
}

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

可以替换为

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

所以我不认为由于这个闭包会引用Foo对象。所以Foo对象可以被GCed。我错了。您可以做的一件事是编写一个小程序,编译它,并检查ILDASM工具中生成的IL。