Jua*_*dio 5 .net c# reflection constructor default-constructor
我的目标是.NET 3.5 SP1,我正在使用CommentChecker验证我的XML文档,一切正常,直到我进入这样的类:
/// <summary>
/// documentation
/// </summary>
public sealed class MyClass {
/// <summary>
/// documentation
/// </summary>
public void Method() {
}
}
Run Code Online (Sandbox Code Playgroud)
在上面的例子中,据我所知,编译器为我的类生成一个默认构造函数.这个问题是CommentChecker会生成警告,告诉我构造函数缺少注释.
我试图修改程序以检测这种特殊情况并忽略它但我被卡住了,我已经尝试过IsDefined(typeof(CompilerGeneratedAttribute), true)
但是没有用.
简而言之,我如何使用反射检测默认构造函数?
如果您愿意深入研究 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();
Run Code Online (Sandbox Code Playgroud)
您可以拒绝任何没有七个字节的方法体。
// Feel free to put this in a constant.
if (body.Length != 7) return false;
Run Code Online (Sandbox Code Playgroud)
原因在后面的代码中很明显。
在ECMA-335(公共语言基础设施 (CLI) 分区 I 到 VI)的I.8.9.6.6 节中,它规定了 CLS 规则 21:
CLS 规则 21:对象构造函数应在对继承的实例数据进行任何访问之前调用其基类的某个实例构造函数。(这不适用于不需要构造函数的值类型。)
这意味着在做任何其他事情之前,必须调用a 基构造函数。我们可以在 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>
Run Code Online (Sandbox Code Playgroud)
我们现在可以开始检查字节:
// 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;
Run Code Online (Sandbox Code Playgroud)
现在是元数据令牌。该call
指令要求以元数据标记的形式与构造函数一起传递方法描述符。这是这是通过暴露的一个四字节的值MetadataToken
属性的MemberInfo
类(从中ConstructorInfo
导出)。
我们可以检查元数据令牌是否有效,但是因为我们已经检查了方法主体的字节数组的长度(七个字节),并且我们知道只剩下一个字节要检查(前两个操作码+ 四字节元数据标记 = 六字节),我们不必检查它是否是无参数构造函数;如果有参数,就会有其他操作码将参数压入堆栈,扩展字节数组。
最后,如果在构造函数中没有做任何其他事情(表明编译器生成了一个只调用基类的构造函数),ret
则将在调用元数据令牌后发出一条指令:
(0x2A) ret
Run Code Online (Sandbox Code Playgroud)
我们可以这样检查:
return body[6] == 0x2a;
}
Run Code Online (Sandbox Code Playgroud)
需要注意为什么调用该方法MightBeCSharpCompilerGenerated
,重点是Might。
假设您有以下课程:
public class Base { }
public class Derived : Base { public Derived() { } }
Run Code Online (Sandbox Code Playgroud)
在没有优化的情况下编译时(通常是DEBUG
模式),C# 编译器将为类插入一些nop
代码(大概是为了帮助调试器),Derived
这会导致调用MightBeCSharpCompilerGenerated
返回 false。
然而,当优化被打开时(通常是RELEASE
模式),C# 编译器将发出不带nop
操作码的七字节方法体,所以它看起来像Derived
有一个编译器生成的构造函数,即使它没有。
这就是该方法被命名Might
而不是Is
or 的原因Has
;它表明可能有一种方法需要您查看,但不能确定。换句话说,你永远不会得到假阴性,但你仍然需要调查是否得到阳性结果。
无法通过元数据检测自动生成的默认构造函数.您可以通过创建一个包含两个类的类库来测试它,一个类具有显式默认构造函数,另一个类没有.然后在程序集上运行ildasm:两个构造函数的元数据是相同的.
我只是更改程序以允许在任何默认构造函数上丢失文档,而不是尝试检测生成的构造函数.大多数文档生成程序,如NDoc和SandcastleGUI,都可以选择将标准文档添加到所有默认构造函数中; 所以根本没有必要记录它们.如果代码中有一个显式的默认构造函数,则可以在构造函数上方放置三个斜杠(///) - 没有别的 - 来禁用Visual Studio关于缺少文档的警告.
归档时间: |
|
查看次数: |
1278 次 |
最近记录: |