几天前,我问why delegates are reference types,基于我误导的观念,即委托所需要的只是两个引用:一个是对象,一个是函数。我完全忽略了(不是因为我不知道,仅仅因为我忘了)是在.NET中,委托至少部分来支持事件作为{的内置实现{3}},这意味着每个代理都通过调用列表支持多个订阅者。
这让我想到,代表们在.NET世界中真正扮演两个不同的角色。一个是简单的函数指针,例如:
Action<string> writeLine = Console.WriteLine;
其他是可观察的
。textBox.TextChanged += HandleTextChanged;
调用列表的存在似乎仅适用于第二个角色,就像上面的简单writeLine
示例一样,您通常不会认为关于订阅者。
实际上,在我看来,可能是两种不同的“种类”代理:“函数指针”类型和“可观察”类型。在我看来,前者可能是一种价值类型。
现在,我不是在争论这个应该的情况,如果它甚至可能的话。我确信在常规代理和多播代理之间区分会有很多缺点,例如,如果委托是值类型,可能需要引入新关键字(multicast
?不可避免的开发人员混淆等等。我真正很想知道的是,从CLR的角度来看, 是否可能可以使用一个可以作为函数指针的值类型。 / p>
我想另一种问这个问题的方式是:System.Delegate
,它的调用列表和所有,基本上是一个基本的CLR类型;或者它是一个简单的“函数引用”类型的包装器,它不会被任何CLR语言暴露?
我为我所使用的所有非正式用语道歉,这可能会使一些受过更多教育的开发人员感到困惑。
答案 0 :(得分:11)
在CLR的早期阶段,曾经存在System.Delegate(类似函数指针)和System.MulticastDelegate(类似事件)之间的区别。在.NET 1.0发布之前就已经废弃了,没有办法创建派生自Delegate的委托类型的实例。它没有必要。 MulticastDelegate经过优化,只有在有多个订户时才创建其调用列表。 System.Delegate由于某种原因而被保留,可能需要做太多工作才能将其删除。
答案 1 :(得分:4)
在我看来,在绝大多数情况下,.NET开发人员发现单播/多播和封闭/开放实例的“统一”支持非常值得花费很小的开销。如果你属于少数民族的情况,这是不幸的,但如果你不介意打破成语和重新发明大量的轮子,那就有办法解决它。稍后会有更多的事情发生。
实际上,问题的意图并不是那么明确,但我会尝试解决各个问题。
我真正很想知道的是,如果有可能, 从CLR的角度来看,要有一个可以充当a的值类型 函数指针。
当然。实际上,代理是使用使用本机int大小的值类型函数指针(在托管世界中IntPtr
)构建的。所有类型安全的铃声和口哨都是在之上构建的。
Action<string> writeLine = Console.WriteLine;
示例的IL看起来像这样:
// Push null-reference onto stack.
// (Console.WriteLine is a static method)
ldnull
// Push unmanaged pointer to desired function onto stack.
ldftn void [mscorlib]System.Console::WriteLine(string)
// Create delegate and push reference to it onto stack.
instance void [mscorlib]System.Action`1<string>::.ctor(object, native int)
// Pop delegate-reference from top of the stack and store in local.
stloc.0
Action<T>
构造函数非常,方便地声明为:
// First arg is 'this' for closed-instance delegates.
// Second arg is pointer to function.
public Action(object @object, IntPtr method);
我想另一种问这个问题的方法是:是System.Delegate,有 它的调用列表和所有,基本上是一个基本的CLR类型;或是 它是一个简单的“函数引用”类型的包装器 没有任何CLR语言公开?
嗯,它是两个一个基本的CLR类型(在执行引擎知道它并特别对待它的意义上)和围绕一个“包装器”函数引用“ - System.Delegate
将指向函数的指针存储为字段(以及关闭实例委托的对象引用)。由于需要存储多播调用列表,因此通过其子类System.MulticastDelegate
进行多播支持显然要复杂得多。通过以下事实实现统一:野外的所有委托类型都必须从System.MulticastDelegate
继承。
我认为“函数引用”是暴露给CLR语言 - 可以通过委托的MethodInfo
属性获得表示方法的Target
,并且从那里关联方法句柄和函数指针。
但你可能已经知道了这一切。在我看来,你的真正的问题是:
我如何构建一个轻量级,类型安全,类似委托的值类型 除了指向.NET中托管函数的指针外什么都没有?
鉴于我到目前为止所提到的一切,这真的很容易:
// Cool but mostly useless lightweight (in space, not time)
// type-safe delegate-like value-type. Doesn't support closed-instance scenarios
// in the interests of space, but trivial to put in if desired.
public struct LeanDelegate<TDelegate>
{
// The only storage required.
private readonly IntPtr _functionPointer;
public LeanDelegate(TDelegate source)
{
if (source == null)
throw new ArgumentNullException("source");
var del = source as Delegate;
if (del == null)
throw new ArgumentException("Argument is not a delegate", "source");
if (del.Target != null)
throw new ArgumentException("Delegate is a closed-instance delegate.", "source");
if (del.GetInvocationList().Length > 1)
throw new ArgumentException("Delegate is a multicast delegate.", "source");
// Retrieve and store pointer to the delegate's target function.
_functionPointer = del.Method.MethodHandle.GetFunctionPointer();
}
// Creates delegate-instance on demand.
public TDelegate Delegate
{
get
{
if (_functionPointer == IntPtr.Zero)
throw new InvalidOperationException("Uninitialized LeanDelegate instance.");
// Use the aforementioned compiler-generated constructor
// to generate the delegate instance.
return (TDelegate)Activator.CreateInstance
(typeof(TDelegate), null, _functionPointer);
}
}
}
然后你可以这样做:
var del = new LeanDelegate<Action<string>>(Console.WriteLine);
del.Delegate("Hello world");
你获得了什么? 能够存储指向任意静态方法的指针(如果委托是开放实例的方法,则为实例方法)并以类型安全的方式执行它,所有这些都在机器指针大小空间中(不包括临时分配)。
你失去了什么? 封闭实例功能。多播。与许多其他API直接兼容。速度(几乎可以肯定),虽然可以设想变通方法。对使用您的API的开发人员的喜爱。
答案 2 :(得分:3)
这里的一个问题是任务的原子性。如果您想到一个包含目标和指向方法的指针的结构。这个结构有两个指针的大小。 CLR仅保证指针大小变量的原子写入,因此这需要一些特殊机制来保持目标和方法同步。但是,如果攻击者设法在类型不匹配的对象上调用函数,则他可能会绕过沙箱。
答案 3 :(得分:0)
是
System.Delegate
,其调用列表和所有,基本上是一个基本的CLR类型;或者它是一个简单的“函数引用”类型的包装器,它不会被任何CLR语言暴露?
我认为既不。首先,虽然System.Delegate
有一些CLR实现的方法,但它的实例不是专门处理的,只是在C#和其他语言中处理(它只是一个带有Invoke
方法的类和其他一些类)。其次,有一个名为fnptr
的特殊CLR类型,表示函数指针(参见this question)。但是,此类型是可访问的,但不是在C#中,而是在CIL(method void *()
)或C ++ / CLI(void (*foo)()
)中。