5

假设我有以下代码:

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 中发生了什么吗?

4

4 回答 4

5

更新:无需求助于 IL 即可查看实际发生的情况:使用反射器来理解匿名方法和捕获的变量


当您使用:

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

你是含蓄的意思this.y;因此,就代表而言,它是对它的引用Foo。因此,Bar(通过委托)的实例保持整个Foo活动(不是垃圾收集),直到Bar可用于收集。

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

在序列化方面......嗯,我要说的第一件事是序列化代表是一个非常非常糟糕的主意。但是,BinaryFormatter will walk 委托,并且您可以(理论上)最终得到一个 serialized Bar、一个 serializedFoo和一个 serialized 委托来链接它们——但前提是您标记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()thisFooBar

于 2009-08-15T08:08:12.507 回答
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();


}
}

这是一个链接......更复杂一点:序列化匿名代表

于 2009-08-14T17:40:09.463 回答
0

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

于 2009-08-14T17:36:17.803 回答
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。

于 2009-08-14T18:14:25.277 回答