关于被接受的答案的快速注释: 我不同意Jeffrey的回答中的一个小部分, 即Delegate
必须是引用类型,因此所有委托都是引用类型。这个观点并不正确,多级继承链并没有排除值类型的可能性;例如,所有枚举类型都继承自System.Enum
,后者又继承自System.ValueType
,后者再继承自System.Object
,全部是引用类型。然而,我认为重要的认识实际上是所有委托不仅从Delegate
继承,而且从MulticastDelegate
继承。正如Raymond在对他的答案的评论中指出,一旦你决定支持多个订阅者,对于委托本身,没有使用引用类型的必要,考虑到数组的需要。
请注意底部的更新。
如果我这样做:
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;
这个问题有两种可能的解释:1. 正确实现委托作为值类型需要额外的工作/复杂度,因为支持修改本地变量值的闭包等内容需要编译器生成的引用类型。
2. 委托不能作为值类型实现的其他原因。
总之,这不会影响我的睡眠,只是我一直很好奇而已。
更新:作为回应Ani的评论,我明白了上面示例中的CompilerGenerated类型为什么也可以是引用类型,因为如果委托将包括函数指针和对象指针,则无论如何都需要引用类型(至少对于使用闭包的匿名函数而言,因为即使您引入一个附加的泛型类型参数,例如Action,这也无法涵盖无法命名的类型!)。 然而,所有这些只是让我有点后悔把闭包的编译器生成类型的问题带入讨论! 我的主要问题是关于委托的,即具有函数指针和对象指针的东西。 对我来说,它似乎仍然可以是值类型。 换句话说,即使是这样...
Action foo = () => { obj.Foo(); };
如果闭包需要创建一个引用类型对象(来支持闭包,并提供给委托器一个引用对象),那么为什么需要创建两个对象(一个支持闭包的对象加上Action
委托器)?
*是的,是的,这是实现细节,我知道!我真正想说的是短期内存存储。
null
吗?空值并不总是理想的选择。 - user395760