使用C#中的reflection检测编译器生成的默认构造函数
我的目标是.NET 3.5 SP1,我正在使用CommentCheckervalidation我的XML文档,一切正常,直到我进入这样的类:
/// /// documentation /// public sealed class MyClass { /// /// documentation /// public void Method() { } }
在上面的例子中,据我所知,编译器为我的类生成一个默认构造函数。 这样做的问题是CommentChecker会生成警告,告诉我构造函数缺少注释。
我试图修改程序以检测这种特殊情况并忽略它,但我卡住了,我已经尝试过IsDefined(typeof(CompilerGeneratedAttribute), true)
但是没有用。
简而言之,我如何使用reflection检测默认构造函数?
无法通过元数据检测自动生成的默认构造函数。 您可以通过创建一个包含两个类的类库来测试它,一个类具有显式默认构造函数,另一个类没有。 然后在程序集上运行ildasm:两个构造函数的元数据是相同的。
我只是更改程序以允许缺少任何默认构造函数的文档,而不是尝试检测生成的构造函数。 大多数文档生成程序,如NDoc和SandcastleGUI,都可以选择向所有默认构造函数添加标准文档; 所以根本没有必要记录它们。 如果代码中有明确的默认构造函数,则可以在构造函数上方放置三个斜杠(///) – 其他任何内容 – 以禁用有关缺少文档的Visual Studio警告。
如果你愿意在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(公共语言基础结构(CLI)分区I至VI)的第I.8.9.6.6节中,它规定了CLS规则21:
CLS规则21:在inheritance实例数据的任何访问之前,对象构造函数应调用其基类的某个实例构造函数。 (这不适用于不需要构造函数的值类型。)
这意味着在完成任何其他操作之前, 必须调用基础构造函数。 我们可以在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
我们现在可以开始检查字节:
// 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
指令要求方法描述符以元数据标记的forms与构造函数一起传递。 这是一个四字节值,通过MemberInfo
类 ( ConstructorInfo
派生自其中)的MetadataToken
属性公开。
我们可以检查元数据令牌是否有效,但是因为我们已经检查了方法体的字节数组的长度(七个字节),并且我们知道只剩下一个字节要检查(前两个操作码) +四字节元数据标记=六个字节),我们不必检查它是否是无参数构造函数; 如果有参数,则会有其他操作码来推送堆栈上的参数,扩展字节数组。
最后,如果在构造函数中没有做任何其他事情 (表明编译器生成的构造函数除了调用基ret
什么都不做),则在调用元数据标记之后会发出一条ret
指令:
(0x2A) ret
我们可以这样检查:
return body[6] == 0x2a; }
需要注意的是为什么该方法被称为MightBeCSharpCompilerGenerated
,重点是Might 。
假设您有以下课程:
public class Base { } public class Derived : Base { public Derived() { } }
在没有优化的情况下进行编译(通常是DEBUG
模式)时,C#编译器将为Derived
类插入一些nop
代码 (可能是为了协助调试器),这会导致调用MightBeCSharpCompilerGenerated
返回false。
但是,当打开优化(通常是RELEASE
模式)时,C#编译器将发出没有nop
操作码的七字节方法体,因此它看起来像Derived
有一个编译器生成的构造函数,即使它没有。
这就是为什么该方法被命名为Might
而不是Is
或Has
; 它表明可能有一种方法需要查看,但不能肯定地说。 换句话说,你永远不会得到假阴性,但你仍然需要调查你是否得到了积极的结果。
以下代码将返回类型中任何无参数构造函数的信息:
var info = typeof(MyClass).GetConstructor(new Type[] {});
我不知道区分默认构造函数和显式指定的无参数构造函数的方法。
CommentChecker问题的一个可能的解决方法是显式创建无参数构造函数,其中需要一个并对其进行适当的注释。