Определить сгенерированный компилятором конструктор по умолчанию, используя отражение в C#

Я нацеливаюсь на.NET 3.5 с пакетом обновления 1 (SP1) и использую CommentChecker для проверки документации XML, все работает нормально, пока я не доберусь до такого класса:

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

В приведенном выше примере, как я понимаю, компилятор создает конструктор по умолчанию для моего класса. Проблема в том, что CommentChecker генерирует предупреждения о том, что в конструкторе отсутствуют комментарии.

Я пытался изменить программу, чтобы обнаружить этот особый случай и игнорировать его, но я застрял, я уже пытался с IsDefined(typeof(CompilerGeneratedAttribute), true) но это не сработало.

Короче говоря, как я могу определить конструктор по умолчанию, используя отражение?

3 ответа

Решение

Нет способа обнаружить автоматически сгенерированные конструкторы по умолчанию через метаданные. Вы можете проверить это, создав библиотеку классов с двумя классами, один с явным конструктором по умолчанию, а другой без. Затем выполните сборку ildasm: метаданные двух конструкторов идентичны.

Вместо того, чтобы пытаться обнаружить сгенерированные конструкторы, я просто изменил бы программу, чтобы разрешить отсутствующую документацию по любому конструктору по умолчанию. Большинство программ генерации документации, таких как NDoc и SandcastleGUI, имеют возможность добавлять стандартную документацию ко всем конструкторам по умолчанию; так что на самом деле нет необходимости документировать их вообще. Если у вас есть явный конструктор по умолчанию в вашем коде, вы можете поставить три слеша (///) над конструктором - ничего больше - чтобы отключить предупреждение Visual Studio об отсутствующей документации.

Если вы готовы немного покопаться в ИЛ, то можете пройти большую часть пути туда.

Во-первых, если у вас есть 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;

Причина будет очевидна в следующем коде.

В разделе I.8.9.6.6 документа ECMA-335 (разделы I-VI инфраструктуры общего языка (CLI)) говорится о правиле 21 CLS:

Правило 21 CLS: конструктор объекта должен вызвать некоторый конструктор экземпляра своего базового класса, прежде чем произойдет какой-либо доступ к унаследованным данным экземпляра. (Это не относится к типам значений, которые не обязательно должны иметь конструкторы.)

Это означает, что прежде чем что- либо еще сделать, должен быть вызван базовый конструктор. Мы можем проверить это в 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 вернуть ложь.

Тем не менее, когда оптимизация включена (как правило, RELEASE режим), компилятор C# будет выдавать семибайтовое тело метода без nop опкоды, так это будет выглядеть Derived имеет конструктор, сгенерированный компилятором, хотя его нет.

Вот почему метод назван Might вместо Is или же Has; это указывает на то, что существует метод, на который вам нужно обратить внимание, но точно сказать не могу. Другими словами, вы никогда не получите ложный отрицательный результат, но вам все равно придется исследовать, если вы получите положительный результат.

Следующий код вернет информацию о любых параметрах без параметров в вашем типе:

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

Я не знаю способа различения конструктора по умолчанию и явно заданного конструктора без параметров.

Возможный обходной путь для вашей проблемы CommentChecker состоит в том, чтобы явно создать конструктор без параметров, где он требуется, и прокомментировать его соответствующим образом.

Другие вопросы по тегам