6

我的目标是 .NET 3.5 SP1,我正在使用CommentChecker来验证我的 XML 文档,一切正常,直到我进入这样的课程:

/// <summary>
/// documentation
/// </summary>
public sealed class MyClass {
    /// <summary>
    /// documentation
    /// </summary>
    public void Method() {
    }
}

据我了解,在上面的示例中,编译器为我的类生成了一个默认构造函数。问题在于 CommentChecker 会生成警告,告诉我构造函数缺少注释。

我试图修改程序以检测这种特殊情况并忽略它,但我被卡住了,我已经尝试过IsDefined(typeof(CompilerGeneratedAttribute), true)但没有奏效。

简而言之,如何使用反射检测默认构造函数?

4

3 回答 3

6

如果您愿意深入研究 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属性(从中派生)公开。MemberInfoConstructorInfo

我们可以检查元数据标记是否有效,但是因为我们已经检查了方法体的字节数组的长度(七个字节),并且我们知道只剩下一个字节需要检查(前两个操作码+ 四字节元数据令牌 = 六字节),我们不必检查它是否是无参数构造函数;如果有参数,就会有其他操作码将参数压入堆栈,扩展字节数组。

最后,如果在构造函数中没有做任何其他事情(表明编译器生成了一个除了调用基础之外什么都不做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而不是Isor的原因Has;它表明您可能需要查看一种方法,但不能确定。换句话说,您永远不会得到假阴性,但您仍然需要调查是否得到阳性结果。

于 2012-09-10T14:58:57.363 回答
5

无法通过元数据检测自动生成的默认构造函数。您可以通过创建一个包含两个类的类库来测试这一点,一个具有显式默认构造函数,一个没有。然后在程序集上运行 ildasm:两个构造函数的元数据是相同的。

我不会尝试检测生成的构造函数,而是简单地更改程序以允许在任何默认构造函数上缺少文档。大多数文档生成程序,如 NDoc 和 SandcastleGUI,都可以选择将标准文档添加到所有默认构造函数;所以根本没有必要记录它们。如果您的代码中有一个显式的默认构造函数,则可以在构造函数上方放置三个斜杠 (///) - 仅此而已 - 以禁用有关缺少文档的 Visual Studio 警告。

于 2010-07-06T22:49:41.437 回答
1

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

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

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

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

于 2010-07-06T22:50:03.430 回答