【问题标题】:Anonymous methods, scope, and serialization匿名方法、范围和序列化
【发布时间】:2010-11-19 17:30:23
【问题描述】:

假设我有以下代码:

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 的,因此,它的作用域不需要发生任何事情。但是你怎么了?当实际评估 b 时,需要保留 y 的值才能返回。是否所有的 Foo 都保留在以后评估 y ?我只能假设 Foo 没有被 GC 处理。

现在假设我们将 Bar 序列化到磁盘,然后再反序列化它。什么实际上已经被序列化了?它是否也对 Foo 进行了序列化?在 Bar 被反序列化之后,发生了什么魔法使得 b 可以被评估?你能解释一下 IL 中发生了什么吗?

【问题讨论】:

    标签: c# delegates scope anonymous-methods


    【解决方案1】:

    更新:无需求助于 IL 即可查看实际发生的情况:Using reflector to understand anonymous methods and captured variables


    使用时:

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

    你的意思是this.y;所以就委托而言,它是对Fooreference。因此,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 的东西是当前实例(this)上隐藏方法(称为&lt;CreateBar&gt;b__0())的委托。所以它传递给Bar的当前Foo的实例。

    【讨论】:

    • 在 C# 编程语言(第 3 版)的第 6.5.3 节中,有一个与这种情况非常相似的示例,它的处理方式与 Marc 解释的编译器在 Foo 上生成的实例方法一样。
    • 马克的回答太棒了!谢谢!
    【解决方案2】:

    我认为 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。

    【讨论】:

    • 我已经检查了 IL,但我对 IL 不是很熟悉,无法弄清楚编译器做了什么特别的事情。
    • 编译器不需要闭包类;它是一个字段,而不是本地方法变量。传递的是this。有关更多信息,请参阅我的答案。
    【解决方案3】:

    在反映要序列化的对象时尝试序列化时出错。

    我的例子:

    [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

    【讨论】:

    • 你说得对,在我的例子中,Foo 也需要被标记为 Serializable。所以这意味着它正在序列化所有的 Foo。
    • @Stefan :不,您的所有类都不需要 Serializable 属性(至少对于 XML 序列化不需要)。此属性用于使用格式化程序(BinaryFormatter、SoapFormatter...)进行序列化
    • @Thomas 对,我正在使用 BinaryFormatter。
    【解决方案4】:

    创建一个快速测试项目以输出值,然后查看它们。它应该回答问题,并可能使您在此过程中学到一些额外的东西。 (大多数回答你问题的人都是这样做的。)

    【讨论】:

    • 我也对 IL 及其理论感到好奇。我已经编写了测试来回答我的一些问题,但我想了解更多实际发生的情况。我认为 SO 上的一些超级聪明人可以提供更多信息。
    • 您可以使用 Reflector 查看您的测试项目生成的 IL,当然专家的 cmets 会更容易理解。
    猜你喜欢
    • 1970-01-01
    • 2010-10-22
    • 1970-01-01
    • 1970-01-01
    • 2013-12-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多