在我看来,在绝大多数情况下,.NET 开发人员发现对单播/多播和封闭/开放实例的“统一”支持非常值得所涉及的少量开销。如果你陷入少数情况,那是不幸的,但如果你不介意打破成语并重新发明很多轮子,那么有办法绕过它。稍后会介绍更多内容。
实际上,这个问题的意图并不是那么清楚,但我会尝试解决个别问题。
我真正想知道的是,如果有可能,
从 CLR 的角度来看,有一个值类型可以充当
函数指针。
当然。实际上,委托是使用本机 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 语言 - 可以通过委托的 Target 属性获得代表方法的 MethodInfo,并从那里获得相关的方法句柄和函数指针。
但是您可能已经知道所有这些。在我看来,您的真实问题是:
我将如何构建一个轻量级、类型安全、类似委托的值类型
只存储指向 .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 的开发人员的爱。