为什么在 IL 代码中找不到委托的 Invoke 方法体?
Why can't I find the Invoke method body of a delegate in IL code?
我为 TestDelegate
反编译了源代码
public delegate int TestDelegate(int a, int b);
为什么我在查看这段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 SumDelegate = Sum;
SumDelegate.Invoke(1, 2);
IL:
IL_001c: callvirt instance int32 Resolvers.Tests.Delegates.TestDelegate::Invoke(int32, int32)
正在生成 IL 显示 Invoke 方法调用,我找不到它。究竟是怎么回事?
因为委托是对方法的引用,而不是实际方法。
它没有在您的 C# 代码上实现,那么是什么让您认为它可以在生成的 IL 代码中有任何类型的实现?
来自Delegates (C# Programming Guide):
A delegate is a type that represents references to methods with a particular parameter list and return type. When you instantiate a delegate, you can associate its instance with any method with a compatible signature and return type. You can invoke (or call) the method through the delegate instance.
委托上的 Invoke(...)
方法(以及其他一些方法,如 BeginInvoke(...)
和 EndInvoke(...)
)由运行时本身实现,而不是在程序集中实现,它这就是为什么在反编译时看不到方法体的原因。这些方法附加了一个属性来指示这一点,例如:
[MethodImpl(0, MethodCodeType=MethodCodeType.Runtime)]
public virtual int Invoke(int a, int b);
问它是如何工作的 "under the hood" 当然是合理的,虽然答案很复杂,因为它取决于你的方法的 种类 委托是调用(例如静态方法与实例方法、虚拟方法与非虚拟方法等)以及委托是 "open" 还是 "closed".
虽然 "open" 和 "closed" 不是我们通常在委托上下文中遇到的术语,但其含义相对简单 - "closed" 委托存储方法的第一个参数在静态方法的情况下将被调用,或者在实例方法的情况下将调用该方法的实例(即 this
),而 "open" 委托则不会。如果您有兴趣,This post 包含更多详细信息。为简单起见,我将仅介绍您最有可能遇到的两种类型 - 实例封闭委托和静态开放委托。
您可能还注意到在您的反编译中您的 TestDelegate
派生自 System.Delegate
(通过 System.MulticastDelegate
),因此继承了 4 个字段,您可以在 . NET Core 运行时源代码 here。以下三个与我们最相关:
object _target;
IntPtr _methodPtr;
IntPtr _methodPtrAux;
值得注意的是,在委托上调用 Invoke(...)
总是做同样的事情 - 它加载委托的 _target
作为第一个参数(例如方法,第一个参数是我们通常所说的 this
), 然后调用 _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);
addDelegate
是实例关闭委托,因为它存储将调用 Add(...)
方法的实例 (testInstance
)。在这种情况下,_target
字段将存储testInstance
,而_methodPtr
存储Test.Add(...)
方法的地址。
当您随后调用 addDelegate.Invoke(...)
(或等效的短格式 addDelegate(...)
)时,testInstance
会从 _target
字段加载到 this
, Add(...)
方法的地址从 _methodPtr
字段加载并被调用,因此几乎与直接调用 testInstance.Add(...)
完全一样。
对于静态开放委托,您可以这样做:
public class Test
{
public static int Add(int a, int b)
{
return a + b;
}
}
var addDelegate = new TestDelegate(Test.Add);
这里,addDelegate
是一个静态的open delegate,是一个稍微复杂一点的场景。在这种情况下没有实例,因为 Test.Add(...)
是静态的,但由于 Invoke(...)
总是以相同的方式工作,如果要在 _methodPtr
中存储指向 Test.Add(...)
的指针,我们会有问题,因为参数会在错误的位置 - _target
的内容会在第一个参数位置,a
和 b
会在第二和第三个参数位置, 当他们需要在第 1 和第 2 时。
为了解决这个问题,指向 Test.Add(...)
的指针被放入 _methodPtrAux
,_target
存储 addDelegate
本身,而 _methodPtr
包含指向称为 "shuffle thunk" 的特殊方法的指针。调用Invoke(...)
时,shuffle thunk将"shuffling"参数处理到合适的位置,然后根据_methodPtrAux
.
中存储的地址调用真正的方法
让 Invoke(...)
总是做同样的事情当然可以从运行时的角度更简单地调用委托,但会导致静态方法的(打开)委托比静态方法的(关闭)委托稍微慢一些由于 运行 shuffle thunk 首先的开销,实例方法。
我为 TestDelegate
public delegate int TestDelegate(int a, int b);
为什么我在查看这段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 SumDelegate = Sum;
SumDelegate.Invoke(1, 2);
IL:
IL_001c: callvirt instance int32 Resolvers.Tests.Delegates.TestDelegate::Invoke(int32, int32)
正在生成 IL 显示 Invoke 方法调用,我找不到它。究竟是怎么回事?
因为委托是对方法的引用,而不是实际方法。
它没有在您的 C# 代码上实现,那么是什么让您认为它可以在生成的 IL 代码中有任何类型的实现?
来自Delegates (C# Programming Guide):
A delegate is a type that represents references to methods with a particular parameter list and return type. When you instantiate a delegate, you can associate its instance with any method with a compatible signature and return type. You can invoke (or call) the method through the delegate instance.
委托上的 Invoke(...)
方法(以及其他一些方法,如 BeginInvoke(...)
和 EndInvoke(...)
)由运行时本身实现,而不是在程序集中实现,它这就是为什么在反编译时看不到方法体的原因。这些方法附加了一个属性来指示这一点,例如:
[MethodImpl(0, MethodCodeType=MethodCodeType.Runtime)]
public virtual int Invoke(int a, int b);
问它是如何工作的 "under the hood" 当然是合理的,虽然答案很复杂,因为它取决于你的方法的 种类 委托是调用(例如静态方法与实例方法、虚拟方法与非虚拟方法等)以及委托是 "open" 还是 "closed".
虽然 "open" 和 "closed" 不是我们通常在委托上下文中遇到的术语,但其含义相对简单 - "closed" 委托存储方法的第一个参数在静态方法的情况下将被调用,或者在实例方法的情况下将调用该方法的实例(即 this
),而 "open" 委托则不会。如果您有兴趣,This post 包含更多详细信息。为简单起见,我将仅介绍您最有可能遇到的两种类型 - 实例封闭委托和静态开放委托。
您可能还注意到在您的反编译中您的 TestDelegate
派生自 System.Delegate
(通过 System.MulticastDelegate
),因此继承了 4 个字段,您可以在 . NET Core 运行时源代码 here。以下三个与我们最相关:
object _target;
IntPtr _methodPtr;
IntPtr _methodPtrAux;
值得注意的是,在委托上调用 Invoke(...)
总是做同样的事情 - 它加载委托的 _target
作为第一个参数(例如方法,第一个参数是我们通常所说的 this
), 然后调用 _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);
addDelegate
是实例关闭委托,因为它存储将调用 Add(...)
方法的实例 (testInstance
)。在这种情况下,_target
字段将存储testInstance
,而_methodPtr
存储Test.Add(...)
方法的地址。
当您随后调用 addDelegate.Invoke(...)
(或等效的短格式 addDelegate(...)
)时,testInstance
会从 _target
字段加载到 this
, Add(...)
方法的地址从 _methodPtr
字段加载并被调用,因此几乎与直接调用 testInstance.Add(...)
完全一样。
对于静态开放委托,您可以这样做:
public class Test
{
public static int Add(int a, int b)
{
return a + b;
}
}
var addDelegate = new TestDelegate(Test.Add);
这里,addDelegate
是一个静态的open delegate,是一个稍微复杂一点的场景。在这种情况下没有实例,因为 Test.Add(...)
是静态的,但由于 Invoke(...)
总是以相同的方式工作,如果要在 _methodPtr
中存储指向 Test.Add(...)
的指针,我们会有问题,因为参数会在错误的位置 - _target
的内容会在第一个参数位置,a
和 b
会在第二和第三个参数位置, 当他们需要在第 1 和第 2 时。
为了解决这个问题,指向 Test.Add(...)
的指针被放入 _methodPtrAux
,_target
存储 addDelegate
本身,而 _methodPtr
包含指向称为 "shuffle thunk" 的特殊方法的指针。调用Invoke(...)
时,shuffle thunk将"shuffling"参数处理到合适的位置,然后根据_methodPtrAux
.
让 Invoke(...)
总是做同样的事情当然可以从运行时的角度更简单地调用委托,但会导致静态方法的(打开)委托比静态方法的(关闭)委托稍微慢一些由于 运行 shuffle thunk 首先的开销,实例方法。