あなたが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();
7バイトを持たないメソッド本体はすべて拒否できます。
// 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:オブジェクトコンストラクターは、継承されたインスタンスデータへのアクセスが発生する前に、その基本クラスのインスタンスコンストラクターを呼び出す必要があります。(これは、コンストラクターを必要としない値型には適用されません。)
これは、他のことを行う前に、基本コンストラクターを呼び出す必要があることを意味します。これは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
プロパティを通じて公開される4バイトの値です。MemberInfo
ConstructorInfo
メタデータトークンが有効であるかどうかを確認できましたが、メソッド本体のバイト配列の長さ(7バイト)をすでに確認しており、確認するバイトが1バイトしか残っていないことがわかっているため(最初の2つのオペコード) +4バイトのメタデータトークン=6バイト)、パラメーターのないコンストラクターに対するものかどうかを確認する必要はありません。パラメータがある場合は、スタックにパラメータをプッシュしてバイト配列を拡張する他のオペコードがあります。
最後に、コンストラクターで他に何も行われていない場合(コンパイラーが、ベースを呼び出すだけのコンストラクターを生成したことを示します)ret
、メタデータトークンの呼び出し後に命令が発行されます。
(0x2A) ret
このように確認できます:
return body[6] == 0x2a;
}
MightMightBeCSharpCompilerGenerated
に重点を置いて、メソッドが呼び出される理由に注意する必要があります。
次のクラスがあるとします。
public class Base { }
public class Derived : Base { public Derived() { } }
最適化なしでコンパイルする場合(通常はDEBUG
モード)、C#コンパイラーはクラスにいくつかのnop
コード(おそらくデバッガーを支援するため)を挿入します。Derived
これにより、呼び出しでMightBeCSharpCompilerGenerated
falseが返されます。
ただし、最適化がオンになっている場合(通常はモード)、C#コンパイラはオペコードRELEASE
なしで7バイトのメソッド本体を出力するため、コンパイラが生成したコンストラクタはないように見えます。nop
Derived
これが、メソッドがorMight
の代わりに名前が付けられている理由です。それはあなたが見なければならない方法があるかもしれないことを示していますが、確かに言うことはできません。つまり、偽陰性が発生することはありませんが、陽性の結果が得られるかどうかを調査する必要があります。Is
Has