【发布时间】:2011-12-15 21:33:34
【问题描述】:
关于已接受答案的快速说明:我不同意Jeffrey's answer 的一小部分,即由于Delegate 必须是引用类型,因此所有代表都是引用类型。 (多级继承链排除值类型是不正确的;例如,所有枚举类型都继承自 System.Enum,而 System.ValueType 又继承自 System.ValueType,后者继承自 System.Object,all 引用类型。)但是我认为,从根本上说,所有委托实际上不仅继承自 Delegate,而且继承自 MulticastDelegate,这是这里的关键实现。正如Raymond points out 在对他的 答案的评论中,一旦你承诺支持多个订阅者,不 对委托本身使用引用类型真的没有意义,考虑到某个地方需要一个数组。
见底部更新。
如果我这样做,我总是觉得奇怪:
Action foo = obj.Foo;
我每次都在创建一个新 Action 对象。我确信成本是最低的,但它涉及分配内存以供以后进行垃圾收集。
鉴于委托本身自身是不可变的,我想知道为什么它们不能是值类型?那么像上面这样的一行代码只会对堆栈上的内存地址进行简单的分配*。
即使考虑匿名函数,这似乎(对我)也可以。考虑以下简单示例。
Action foo = () => { obj.Foo(); };
在这种情况下foo 确实构成了一个闭包,是的。在很多情况下,我想这确实需要一个实际的引用类型(例如当局部变量被关闭并在闭包中被修改时)。 但在某些情况下,它不应该。例如在上面的例子中,支持闭包的类型看起来像这样: 我收回我原来的观点。下面确实需要是一个引用类型(或者:它不需要,但如果它是struct,它无论如何都会被装箱)。所以,忽略下面的代码示例。我只留下它来为特别提到它的答案提供上下文。
struct CompilerGenerated
{
Obj obj;
public CompilerGenerated(Obj obj)
{
this.obj = obj;
}
public void CallFoo()
{
obj.Foo();
}
}
// ...elsewhere...
// This would not require any long-term memory allocation
// if Action were a value type, since CompilerGenerated
// is also a value type.
Action foo = new CompilerGenerated(obj).CallFoo;
这个问题有意义吗?在我看来,有两种可能的解释:
- 将委托正确地实现为值类型需要额外的工作/复杂性,因为支持诸如 确实修改局部变量值的闭包之类的东西无论如何都需要编译器生成的引用类型。
- 还有一些其他原因,说明在底层,委托不能被实现为值类型。
最后,我并没有为此失眠;这只是我一直好奇的事情。
更新:为了回应 Ani 的评论,我明白了为什么上面示例中的 CompilerGenerated 类型也可能是引用类型,因为如果委托将包含一个函数指针和一个对象指针它无论如何都需要一个引用类型(至少对于使用闭包的匿名函数,因为即使你引入了一个额外的泛型类型参数——例如,Action<TCaller>——这也不会涵盖无法命名的类型! )。 然而,这一切都让我后悔将编译器生成的闭包类型问题带入讨论!我的主要问题是关于 delegates,即 with 函数指针和对象指针的东西。在我看来, 仍然可能是一种值类型。
也就是说,即使这样……
Action foo = () => { obj.Foo(); };
...需要创建一个引用类型的对象(支持闭包,并给delegate一些引用),为什么需要创建两个(支持闭包的对象加上Action委托)?
*是的,是的,实现细节,我知道!我真正的意思是短期记忆存储。
【问题讨论】:
-
第一个可能的解释对我来说已经足够了。
-
好的,假设您想将委托实现为具有函数指针和对象指针的值类型。在您的闭包示例中,对象指针指向哪里?您几乎可以肯定需要将
CompilerGenerated结构实例装箱并将其放在堆上(通过转义分析,在某些情况下可以避免这种情况)。 -
@Ani:啊,我明白你的意思了。也许您可以以答案的形式扩展该评论?
-
你真的想使用 Nullable
吗? -
@Ani:如果委托是一个包含函数指针和对象指针的结构,则构造闭包只需要创建一个新的堆对象而不是两个。如果委托是接口类型(我认为它们应该是),闭包只需要创建一个堆对象来保存闭包数据及其方法。
标签: c# .net delegates value-type reference-type