如果您愿意深入研究 IL,那么您就可以获得大部分内容。
首先,假设你有ConstructorInfo
一个你知道是无参数的实例,你可以像这样获取方法体和方法体的字节(我们将开始构建一个扩展方法来做到这一点):
public static bool MightBeCSharpCompilerGenerated(
this ConstructorInfo constructor)
{
// Validate parmaeters.
if (constructor == null) throw new ArgumentNullException("constructor");
// If the method is static, throw an exception.
if (constructor.IsStatic)
throw new ArgumentException("The constructor parameter must be an " +
"instance constructor.", "constructor");
// Get the body.
byte[] body = constructor.GetMethodBody().GetILAsByteArray();
您可以拒绝任何没有七个字节的方法主体。
// Feel free to put this in a constant.
if (body.Length != 7) return false;
原因在下面的代码中很明显。
在ECMA-335 (Common Language Infrastructure (CLI) Partitions I to VI) 的I.8.9.6.6 节中,它规定了 CLS 规则 21:
CLS 规则 21:对象构造函数应在对继承的实例数据进行任何访问之前调用其基类的某个实例构造函数。(这不适用于不需要构造函数的值类型。)
这意味着在完成任何其他操作之前,必须调用基础构造函数。我们可以在 IL 中检查这一点。用于此的 IL 看起来像这样(我在 IL 命令之前将字节值放在括号中):
// Loads "this" on the stack, as the first argument on an instance
// method is always "this".
(0x02) ldarg.0
// No parameters are loaded, but metadata token will be explained.
(0x28) call <metadata token>
我们现在可以开始检查字节了:
// Check the first two bytes, if they are not the loading of
// the first argument and then a call, it's not
// a call to a constructor.
if (body[0] != 0x02 || body[1] != 0x28) return false;
现在是元数据令牌。该call
指令要求方法描述符以元数据令牌的形式与构造函数一起传递。这是一个四字节的值,它通过类的MetadataToken
属性(从中派生)公开。MemberInfo
ConstructorInfo
我们可以检查元数据标记是否有效,但是因为我们已经检查了方法体的字节数组的长度(七个字节),并且我们知道只剩下一个字节需要检查(前两个操作码+ 四字节元数据令牌 = 六字节),我们不必检查它是否是无参数构造函数;如果有参数,就会有其他操作码将参数压入堆栈,扩展字节数组。
最后,如果在构造函数中没有做任何其他事情(表明编译器生成了一个除了调用基础之外什么都不做ret
的构造函数),在调用元数据标记之后会发出一条指令:
(0x2A) ret
我们可以这样检查:
return body[6] == 0x2a;
}
需要注意为什么调用该方法MightBeCSharpCompilerGenerated
,重点是Might。
假设您有以下课程:
public class Base { }
public class Derived : Base { public Derived() { } }
在没有优化的情况下进行编译(通常是DEBUG
模式)时,C# 编译器将为类插入一些nop
代码(可能是为了帮助调试器),Derived
这将导致调用MightBeCSharpCompilerGenerated
返回 false。
然而,当开启优化(通常是RELEASE
模式)时,C# 编译器将发出不带操作码的七字节方法体nop
,因此它看起来就像Derived
有一个编译器生成的构造函数,即使它没有。
这就是为什么该方法被命名Might
而不是Is
or的原因Has
;它表明您可能需要查看一种方法,但不能确定。换句话说,您永远不会得到假阴性,但您仍然需要调查是否得到阳性结果。