匿名方法、范围和序列化
假设我有以下代码:
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 会怎样呢? y 的值需要保留下来,以便在实际计算 b 时返回。 是否所有 Foo 都保留下来以便稍后评估 y ? 我只能假设 Foo 没有被 GC 处理。
现在假设我们将 Bar 序列化到磁盘,然后再将其反序列化。 实际上已经连载了哪些内容? 它也序列化了 Foo 吗? 到底发生了什么魔力,使得在 Bar 反序列化后可以对 b 进行求值? 你能解释一下 IL 中发生了什么吗?
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?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
当它反映要序列化的对象时尝试序列化时出现错误。
我的示例:
这是执行此操作的链接...稍微复杂一点: 序列化匿名委托
I got error when trying to serialize when it was reflecting the object to serialize.
my example:
Here is a link to doing it...little more complex: Serializing Anon Delegates
我认为 Foo 对象中的 x 和 y 将被捕获为值类型。 因此,为该 lambda 表达式创建的闭包不应保留对 Foo 对象的引用。 因此,编译器可能会为该闭包创建一个类:
并
可能被替换为
因此,我认为该闭包不会导致对 Foo 对象的引用。 所以 Foo 对象可以被 GC 处理。 我可能是错的。 您可以做的一件事是编写一个小程序,对其进行编译,然后在 ILDASM 工具中检查生成的 IL。
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:
and
may be replaced by
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.
创建一个快速测试项目来输出值,然后查看它们。 它应该回答问题,并且可能会让您在这个过程中学到一些额外的东西。 (这是大多数回答你问题的人所做的。)
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.)
更新:查看实际发生的情况,而不必求助于 IL: 使用反射器来理解匿名方法和捕获的变量
当你使用时:
你隐含着
this.y
; 因此,就委托而言,它包含对Foo
的引用。 因此,Bar
的实例(通过委托)使整个Foo
保持活动状态(不会被垃圾回收),直到Bar
可用于收藏。特别是,(在这种情况下)编译器不需要生成额外的类来处理捕获的变量; 唯一需要的是
Foo
实例,因此可以在Foo
上生成一个方法。 如果委托涉及局部变量(this
除外),情况会更加复杂。就序列化而言......好吧,我要说的第一件事是序列化委托是一个非常非常糟糕的主意。 但是,
BinaryFormatter
将遍历委托,并且您(理论上)最终可以得到一个序列化的Bar
、一个序列化的Foo 和一个序列化委托来链接它们 - 但仅如果您将
Foo
标记为[Serialized]
。但我强调 - 这是一个坏主意。 我很少使用 BinaryFormatter(出于多种原因),但我看到使用它的人的一个常见问题是“为什么它尝试序列化(某种随机类型)”。 通常,答案是“您正在发布一个事件,并且它正在尝试序列化订阅者”,在这种情况下,最常见的修复方法是将事件的字段标记为
[NonSerialized]
。而不是看IL; 研究此问题的另一种方法是在 .NET 1.0 模式下使用反射器(即不交换匿名方法); 然后你可以看到:
如你所见; 传递给
Bar
的是当前实例 (this
) 上隐藏方法(称为b__0()
)的委托。 因此它是传递给Bar
的当前Foo
的实例。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:
You are implicitly meaning
this.y
; so in terms of the delegate, it is the reference toFoo
that is included. As such, the instance ofBar
(via the delegate) keeps the entireFoo
alive (not garbage-collected) until theBar
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 onFoo
. This would be be more complex if the delegate involved local variables (other thanthis
).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 serializedBar
, a serializedFoo
, and a serialized delegate to link them - but only if you markFoo
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:
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 currentFoo
that is passed to theBar
.