.NET中的委托:它们是如何构造的?

14

在检查C#和.NET中的委托时,我注意到了一些有趣的事实:

在C#中创建委托会创建一个派生自MulticastDelegate的类,并带有一个构造函数:

.method public hidebysig specialname rtspecialname instance 
   void .ctor(object 'object', native int 'method') runtime managed { }
意味着它需要实例和方法的指针。然而,在C#中构造委托的语法表明它有一个构造函数。
new MyDelegate(int () target)
在C#中,我们可以将int()识别为函数实例(在C++中,int *target()则是一个函数指针)。显然,C#编译器从由函数名定义的方法组中挑选出正确的方法并构造委托。那么第一个问题是,C#编译器(或者确切地说是Visual Studio)从哪里获取这个构造函数签名?我没有注意到任何特殊的属性或者东西可以进行区分。这是一种编译器/Visual Studio的魔法吗?如果不是,T (args) target在C#中是否有效呢?我无法使它编译通过,例如:

int() target = MyMethod;

是无效的,对MyMetod做任何事情也是无效的,例如调用.ToString()(这有一定的道理,因为那技术上是一个方法组,但我想应该可以通过强制转换明确地选择一个方法,例如(int())MyFunction)。所以这全部都是编译器的魔法吗?通过反射查看构造过程还显示了另一种语法:

Func CS$1$0000 = new Func(null, (IntPtr) Foo);

这与反汇编的构造函数签名一致,但它无法编译通过!最后一个有趣的说明是DelegateMulticastDelegate类有另一组构造函数:

.method family hidebysig specialname rtspecialname instance void .ctor(class System.Type target, string 'method') cil managed

在哪里将实例和方法指针转换为类型和字符串方法名?这可以通过自定义委托构造函数签名中的runtime managed关键字来解释,即运行时是否在这里发挥作用?编辑:我想表达的是,委托构造不仅涉及到C#编译器/CLR的魔法,还涉及到一些Visual Studio的魔法,因为Intellisense在建议构造函数参数时会翻转出一些新的语法,并隐藏其中一个参数(例如Reflector没有使用这个语法,事实上也没有使用这个构造函数)。我想知道这种说法是否正确,以及函数实例语法在C#中是否有更深层次的含义,还是只是由Visual Studio魔法部分实现的常量格式,以提高可读性(这有意义,因为它看起来像无效的C#代码)?简而言之,如果我正在实现Intellisense,我需要为委托做一些魔法,还是可以通过一些聪明的机制构造建议?最终编辑:那么,普遍的共识是这确实是Visual Studio的魔法。看到其他类似的例子(请参见Marc Gravell的评论)证明了这一点。

CS$1$0000是反编译生成的,无法编译。 - Dykam
2
我知道,名字不是重点,重点是新的构造无法编译。 - Saulius Valatka
是的,只是想指出。 - Dykam
3个回答

12

第一个参数从对象引用(或static方法的null)解析出来,没有魔法。

然而,关于第二个参数 - 它是一个未管理的指针(native int);简而言之,没有其他可用的直接C#语法可以使用此构造函数 - 它使用特定的IL指令(ldftn)从元数据中解析函数。不过,您可以使用Delegate.CreateDelegate通过反射创建委托。您也可以使用IL emit (DynamicMethod等),但这并不好玩。


好的,所以C#不支持方法指针,但是,“函数实例”类似的语法从哪里来呢?我理解从一个有两个参数的构造函数到一个只有一个参数的构造函数是编译器的魔法,但是Visual Studio从哪里得到建议使用单个参数构造函数的想法(我在其他地方找不到)? - Saulius Valatka
1
@Saulius - 这是语言规范所要求的,它没有涉及构造函数,但它指出语法是(例如)new EventHandler(someObject.SomeMethod); - 我认为这里有一个“输入”,即使编译器对其进行了一些魔法以生成 IL。 - Marc Gravell
@Marc Gravell:那么关于我的Visual Studio魔法断言(请查看帖子的编辑部分),您有什么看法?这部分让我感到困扰-当然只是出于好奇 :) - Saulius Valatka
1
Intellisense 实际上并不知道生成的 IL。它使用符号数据库和任何可用的 XML 文档注释来理解程序中找到的符号。当遇到委托参数时,Intellisense 知道特定的委托定义,因此可以显示适当的提示信息。这是 Visual Studio 的一个功能,与编译器提供的便利功能无关,使委托更易于使用。 - Dan Bryant
1
@Dan Bryant:Visual Studio不需要文档XML文件来获取签名,只需要用于注释 - 我假设签名来自反射程序集,因为这是我添加到项目中以使用智能感知的全部内容。由于我一直将系统程序集视为普通程序集,所以发现这种不一致性有点令人惊讶。 - Saulius Valatka
@Saulius - 关于编辑的问题;说实话,我从来没有深入研究过它(VS处理方式)。我假设它是一个特殊情况,就像在string上隐藏IEnumerable<char>扩展方法一样(尽管它们编译得很好)。 - Marc Gravell

0

首先定义委托(这是 Visual Studio 知道目标方法签名的方式):

delegate void MyDelegate();

然后您可以像这样构建委托实例:

MyDelegate method = new MyDelegate({method name});

// If this was the method you wanted to invoke:
void MethodToInvoke()
{
    // do something
}

MyDelegate method = new MyDelegate(MethodToInvoke);

C#会自动选择与委托签名匹配的方法。

编辑:当Visual Studio的Intellisense向您显示int () target建议时,它向您展示了您可以使用的C#方法的签名。 C#编译器将C#表示形式转换为IL。 IL实现看起来会有所不同,因为IL不是C风格的语言,而C#编译器提供了语法糖来抽象化实现细节。


1
我认为他的意思是从低层面的角度来看。 - Jamie Keeling
@Saulius:int() target 本身不是有效的C#代码,但通过什么其他方式他们会在智能感知中表示目标呢? - Zach Johnson
这就是问题所在 - 他们必须展示“某些东西”,所以他们只是展示一些虚构的语法以便于理解(仅仅是为了委托而进行的Visual Studio魔法),还是它具有更深层次的意义? :) - Saulius Valatka
@Saulius:我想这只是为了清晰起见而编造的语法。 - Zach Johnson

0
这只是一个猜测,如果我错了,请不要开枪打我,但我认为Intellisense从委托上定义的Invoke方法获取目标方法的签名。反射器清楚地显示 System.Action<T> 上的Invoke方法是:
[MethodImpl(0, MethodCodeType=MethodCodeType.Runtime)]
public virtual void Invoke(T obj);

这与Intellisense提供的签名建议相同。 Intellisense的魔力在于,当发现委托类型时,它会查看Invoke方法并建议一个接受与其匹配的目标的构造函数。


是的,签名是 Invoke 方法的签名,但是 VS 显示的 语法 是无效的 C# ! - Saulius Valatka
@Saulius:VS显示的工具提示仅显示目标方法的基本签名,而不是因目标而异的所有额外信息(例如参数名称和可访问性修饰符)。正如Marc所指出的那样,这是一种特殊情况,其中Intellisense会输出一些伪代码,而不是从程序集元数据中读取的内容。 - Rory

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接