假设我有以下代码:
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中发生的事情吗?
答案 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。