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与字节码相差甚远。。。。个人