使用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而不是IsHas ; 它表明可能有一种方法需要查看,但不能肯定地说。 换句话说,你永远不会得到假阴性,但你仍然需要调查你是否得到了积极的结果。

以下代码将返回类型中任何无参数构造函数的信息:

 var info = typeof(MyClass).GetConstructor(new Type[] {}); 

我不知道区分默认构造函数和显式指定的无参数构造函数的方法。

CommentChecker问题的一个可能的解决方法是显式创建无参数构造函数,其中需要一个并对其进行适当的注释。