Если вы хотите немного покопаться в ИЛ, тогда вы можете получить большинство пути туда.
Сначала, если у вас есть 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:
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
в 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
для возврата false.
Однако, когда оптимизация включена (обычно режим RELEASE
), компилятор C # будет выдавать семибайтовое тело метода без кодов операций nop
, поэтому он будет выглядетьлайкDerived
имеет конструктор, сгенерированный компилятором, хотя его и нет.
Именно поэтому метод называется Might
вместо Is
или Has
;это означает, что может быть методом, на который вам нужно обратить внимание, но не могу сказать наверняка.Другими словами, вы никогда не получите ложный отрицательный результат, но вам все равно придется расследовать, если вы получите положительный результат.