问题描述
关于已接受答案的快速说明:我不同意 Jeffrey 的回答,即由于 Delegate
必须是引用类型,因此所有委托都是引用类型.(多级继承链排除值类型是不正确的;例如,所有枚举类型都继承自 System.Enum
,而后者又继承自 System.ValueType
,它继承自 System.Object
,all 引用类型.)但我认为,从根本上说,所有委托实际上不仅继承自 Delegate
但来自 MulticastDelegate
是这里的关键实现.正如 Raymond 在对他的的评论中指出em> 回答,一旦你承诺支持多个订阅者,考虑到某个地方需要一个数组,不 对委托本身使用引用类型真的没有意义.
Quick note on the accepted answer: I disagree with a small part of Jeffrey's answer, namely the point that since Delegate
had to be a reference type, it follows that all delegates are reference types. (It simply isn't true that a multi-level inheritance chain rules out value types; all enum types, for example, inherit from System.Enum
, which in turn inherits from System.ValueType
, which inherits from System.Object
, all reference types.) However I think the fact that, fundamentally, all delegates in fact inherit not just from Delegate
but from MulticastDelegate
is the critical realization here. As Raymond points out in a comment to his answer, once you've committed to supporting multiple subscribers, there's really no point in not using a reference type for the delegate itself, given the need for an array somewhere.
见底部更新.
如果我这样做,我总是觉得奇怪:
It has always seemed strange to me that if I do this:
Action foo = obj.Foo;
我每次都在创建一个 new Action
对象.我确信成本是最低的,但它涉及分配内存以供以后进行垃圾收集.
I am creating a new Action
object, every time. I'm sure the cost is minimal, but it involves allocation of memory to later be garbage collected.
鉴于委托本身自身是不可变的,我想知道为什么它们不能是值类型?那么像上面这样的一行代码只会对堆栈上的内存地址进行简单的分配*.
Given that delegates are inherently themselves immutable, I wonder why they couldn't be value types? Then a line of code like the one above would incur nothing more than a simple assignment to a memory address on the stack*.
即使考虑匿名函数,这似乎(对我)也可以.考虑以下简单示例.
Even considering anonymous functions, it seems (to me) this would work. Consider the following simple example.
Action foo = () => { obj.Foo(); };
在这种情况下,foo
确实构成了一个闭包,是的.在许多情况下,我想这确实需要一个实际的引用类型(例如当局部变量被关闭并在闭包中被修改时).但在某些情况下,它不应该.例如在上面的例子中,支持闭包的类型看起来像这样: 我收回我原来的观点.下面确实需要是一个引用类型(或者:它不需要,但如果它是一个struct
,它无论如何都会被装箱).所以,忽略下面的代码示例.我只留下它来为特别提到它的答案提供上下文.
In this case foo
does constitute a closure, yes. And in many cases, I imagine this does require an actual reference type (such as when local variables are closed over and are modified within the closure). But in some cases, it shouldn't. For instance in the above case, it seems that a type to support the closure could look like this: I take back my original point about this. The below really does need to be a reference type (or: it doesn't need to be, but if it's a struct
it's just going to get boxed anyway). So, disregard the below code example. I leave it only to provide context for answers the specfically mention it.
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;
这个问题有意义吗?在我看来,有两种可能的解释:
Does this question make sense? As I see it, there are two possible explanations:
- 将委托正确地实现为值类型将需要额外的工作/复杂性,因为对诸如 修改局部变量值的闭包之类的支持无论如何都需要编译器生成的引用类型.
- 还有一些其他原因导致在底层不能将委托实现为值类型.
- Implementing delegates properly as value types would have required additional work/complexity, since support for things like closures that do modify values of local variables would have required compiler-generated reference types anyway.
- There are some other reasons why, under the hood, delegates simply can't be implemented as value types.
最后,我并没有为此失眠;这只是我一直好奇的事情.
In the end, I'm not losing any sleep over this; it's just something I've been curious about for a little while.
更新:为了回应 Ani 的评论,我明白了为什么上面示例中的 CompilerGenerated
类型也可能是引用类型,因为如果委托要包含一个函数指针和一个对象指针,无论如何它都需要一个引用类型(至少对于使用闭包的匿名函数,因为即使你引入了一个额外的泛型类型参数——例如,Action
——这不会涵盖无法命名的类型!).然而,所有这一切都让我后悔将编译器生成的闭包类型的问题带入讨论!我的主要问题是关于 delegates,即 with 函数指针和对象指针的东西.在我看来, 仍然可能是一种值类型.
Update: In response to Ani's comment, I see why the CompilerGenerated
type in my above example might as well be a reference type, since if a delegate is going to comprise a function pointer and an object pointer it'll need a reference type anyway (at least for anonymous functions using closures, since even if you introduced an additional generic type parameter—e.g., Action<TCaller>
—this wouldn't cover types that can't be named!). However, all this does is kind of make me regret bringing the question of compiler-generated types for closures into the discussion at all! My main question is about delegates, i.e., the thing with the function pointer and the object pointer. It still seems to me that could be a value type.
也就是说,即使这样……
In other words, even if this...
Action foo = () => { obj.Foo(); };
...需要创建一个引用类型的对象(支持闭包,并给delegate一些引用),为什么需要创建两个(支持闭包的对象加上 Action
委托)?
...requires the creation of one reference type object (to support the closure, and give the delegate something to reference), why does it require the creation of two (the closure-supporting object plus the Action
delegate)?
*是的,是的,实现细节,我知道!我真正的意思是短期记忆存储.
推荐答案
问题归结为:CLI(公共语言基础设施)规范说委托是引用类型.为什么会这样?
The question boils down to this: the CLI (Common Language Infrastructure) specification says that delegates are reference types. Why is this so?
一个原因在今天的 .NET Framework 中显而易见.在最初的设计中,有两种委托:普通委托和多播"委托,它们的调用列表中可以有多个目标.MulticastDelegate
类继承自 Delegate
.由于不能从值类型继承,Delegate
必须是引用类型.
One reason is clearly visible in the .NET Framework today. In the original design, there were two kinds of delegates: normal delegates and "multicast" delegates, which could have more than one target in their invocation list. The MulticastDelegate
class inherits from Delegate
. Since you can't inherit from a value type, Delegate
had to be a reference type.
最后,所有实际的委托最终都成为了多播委托,但在这个过程的那个阶段,合并这两个类已经太迟了.请参阅此博文关于这个确切的主题:
In the end, all actual delegates ended up being multicast delegates, but at that stage in the process, it was too late to merge the two classes. See this blog post about this exact topic:
我们放弃了 Delegate 和 MulticastDelegate 之间的区别在V1结束时.那时,它会是一个巨大的更改为合并两个类,所以我们没有这样做.你应该假装它们被合并并且只存在 MulticastDelegate.
We abandoned the distinction between Delegate and MulticastDelegate towards the end of V1. At that time, it would have been a massive change to merge the two classes so we didn’t do so. You should pretend that they are merged and that only MulticastDelegate exists.
此外,委托目前有 4-6 个字段,都是指针.16 字节通常被认为是节省内存仍然胜过额外复制的上限.一个 64 位的 MulticastDelegate
占用 48 个字节.鉴于此,以及他们使用继承的事实表明类是自然选择.
In addition, delegates currently have 4-6 fields, all pointers. 16 bytes is usually considered the upper bound where saving memory still wins out over extra copying. A 64-bit MulticastDelegate
takes up 48 bytes. Given this, and the fact that they were using inheritance suggests that a class was the natural choice.
这篇关于为什么委托是引用类型?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!