C# 为什么可以';我在IL代码中找不到委托的Invoke方法体?

C# 为什么可以';我在IL代码中找不到委托的Invoke方法体?,c#,delegates,cil,C#,Delegates,Cil,我为我的TestDelegate 公共委托inttestdelegate(inta,intb); 当我查看此IL代码时,为什么找不到Invoke方法?我在委托中也找不到其他方法。它是如何工作的 .method public hidebysig virtual newslot instance int32 Invoke( int32 a, int32 b ) runtime managed { // Can't find a body

我为我的
TestDelegate

公共委托inttestdelegate(inta,intb);
当我查看此IL代码时,为什么找不到Invoke方法?我在委托中也找不到其他方法。它是如何工作的

  .method public hidebysig virtual newslot instance int32
    Invoke(
      int32 a,
      int32 b
    ) runtime managed
  {
    // Can't find a body
  } // end of method TestDelegate::Invoke
TestDelegate-sumdegate=Sum;
sumdegate.Invoke(1,2);
IL:


生成IL显示调用方法调用,我找不到它。到底发生了什么?

因为委托是对方法的引用,而不是实际的方法

它在你的c代码上没有实现,那么是什么让你认为它可以在生成的IL代码中有任何类型的实现呢

发件人:

委托是一种类型,它表示对具有特定参数列表和返回类型的方法的引用。实例化委托时,可以将其实例与具有兼容签名和返回类型的任何方法相关联。您可以通过委托实例调用(或调用)该方法

委托上的
Invoke(…)
方法(以及其他一些方法,如
BeginInvoke(…)
EndInvoke(…)
)由运行时本身实现,而不是在程序集中实现,这就是为什么在反编译时看不到方法体的原因。这些方法附加了一个属性来表示这一点,例如:

[MethodImpl(0, MethodCodeType=MethodCodeType.Runtime)]
public virtual int Invoke(int a, int b);
当然,询问它是如何“在引擎盖下”工作的是合理的,尽管答案非常复杂,因为它取决于委托要调用的方法的类型(例如静态方法与实例方法、虚拟方法与非虚拟方法等),以及委托是“打开”还是“关闭”

虽然“打开”和“关闭”不是我们在委托上下文中通常会遇到的术语,但其含义相对简单-一个“关闭”委托将在静态方法中调用的方法的第一个参数存储到该方法中,或将在其上调用该方法的实例中(即
this
)在实例方法的情况下,“open”委托不会。如果您感兴趣,包含更多详细信息。为了简单起见,我将只介绍您最可能遇到的两种类型—实例关闭委托和静态打开委托

您可能还注意到,在反编译过程中,
TestDelegate
源自
System.Delegate
(通过
System.MulticastDelegate
),因此继承了4个字段,您可以在.NET核心运行时源代码中看到这些字段。以下三项与我们最相关:

object _target;
IntPtr _methodPtr;
IntPtr _methodPtrAux;
值得注意的是,对委托调用
Invoke(…)
总是做同样的事情-它加载委托的
\u target
作为第一个参数(例如,第一个参数是我们通常称之为
this
),然后调用
\u methodPtr
所指向的方法,这使得对实例方法的委托非常简单,因为这几乎与直接调用实例方法完全相同,但对于静态方法,这会稍微复杂一些,我们将在下面看到

首先使用最简单的情况,并以您的
TestDelegate
为例,创建一个实例封闭委托,如下所示:

public class Test
{
    private int _c;

    ...

    public int Add(int a, int b)
    {
        return a + b + _c;
    }
}

...

var testInstance = new Test();
var addDelegate = new TestDelegate(testInstance.Add);
public class Test
{
    public static int Add(int a, int b)
    {
        return a + b;
    }
}

var addDelegate = new TestDelegate(Test.Add);
addDelegate
是一个实例关闭的委托,因为它存储了将调用
Add(…)
方法的实例(
testInstance
)。在这种情况下,
\u target
字段将存储
testInstance
,而
\u methodPtr
存储
Test.Add(…)
方法的地址

当您随后调用
addDelegate.Invoke(…)
(或等效的缩写形式
addDelegate(…)
)时,
testInstance
\u target
字段加载到
this
,从
\u methodPtr
字段加载并调用
添加(…)
方法的地址,这几乎和直接调用
testInstance.Add(…)
完全一样

对于静态开放委托,您可以执行以下操作:

public class Test
{
    private int _c;

    ...

    public int Add(int a, int b)
    {
        return a + b + _c;
    }
}

...

var testInstance = new Test();
var addDelegate = new TestDelegate(testInstance.Add);
public class Test
{
    public static int Add(int a, int b)
    {
        return a + b;
    }
}

var addDelegate = new TestDelegate(Test.Add);
这里,
addDelegate
是一个静态开放委托,是一个稍微复杂一些的场景。在这种情况下,没有实例,因为
Test.Add(…)
是静态的,但是由于
Invoke(…)
总是以相同的方式工作,如果它在
\u methodPtr
中存储指向
Test.Add(…)
的指针,我们会遇到一个问题,因为参数位于错误的位置,
\u target
的内容将位于第一个参数位置,
a
b
将位于第二个和第三个参数位置,而它们需要位于第一个和第二个位置

为了解决这个问题,指向
Test.Add(…)
的指针被放在
\u methodPtrAux
中,
\u target
存储
addDelegate
本身,并且
\u methodPtr
包含一个指向称为“shuffle thunk”的特殊方法的指针。调用
Invoke(…)
时,shuffle thunk处理将参数“洗牌”到其正确位置的操作,然后根据
\u methodPtrAux
中存储的地址调用实方法


Invoke(…)
始终执行相同的操作当然会使从运行时的角度调用委托变得更简单,但由于首先运行shuffle thunk的开销,可能会导致(打开)对静态方法的委托比(关闭)对实例方法的委托稍慢。

谢谢您的回答,但是当我调用Invoke()时会发生什么呢?换句话说,委托中的方法是如何调用的?如果委托是分配给某个方法的,它将调用该委托分配给的方法。如果没有,您将因为尝试调用空委托而得到一个
NullReferenceException
。我理解它,我想知道它在内部是如何工作的。调用方法的代码是如何写在字节码上的?字节码?IL与字节码相差甚远。。。。个人