CLR可以支持“函数指针”值类型吗?

时间:2011-10-28 16:42:46

标签: .net delegates clr

几天前,我问why delegates are reference types,基于我误导的观念,即委托所需要的只是两个引用:一个是对象,一个是函数。我完全忽略了(不是因为我不知道,仅仅因为我忘了)是在.NET中,委托至少部分来支持事件作为{的内置实现{3}},这意味着每个代理都通过调用列表支持多个订阅者。

这让我想到,代表们在.NET世界中真正扮演两个不同的角色。一个是简单的函数指针,例如:

Action<string> writeLine = Console.WriteLine;

其他是可观察的

textBox.TextChanged += HandleTextChanged;

调用列表的存在似乎仅适用于第二个角色,就像上面的简单writeLine示例一样,您通常不会认为关于订阅者。

实际上,在我看来,可能是两种不同的“种类”代理:“函数指针”类型和“可观察”类型。在我看来,前者可能是一种价值类型。

现在,我不是在争论这个应该的情况,如果它甚至可能的话。我确信在常规代理和多播代理之间区分会有很多缺点,例如,如果委托是值类型,可能需要引入新关键字(multicast?不可避免的开发人员混淆等等。我真正很想知道的是,从CLR的角度来看, 是否可能可以使用一个可以作为函数指针的值类型。 / p>

我想另一种问这个问题的方式是:System.Delegate,它的调用列表和所有,基本上是一个基本的CLR类型;或者它是一个简单的“函数引用”类型的包装器,它不会被任何CLR语言暴露?

我为我所使用的所有非正式用语道歉,这可能会使一些受过更多教育的开发人员感到困惑。

4 个答案:

答案 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)())中。