まあ、それを修正してください!
構造に継承がない理由、バインドされていないデリゲートの性質を学習します。
また...リフレクションなしでオブジェクトのメソッドを呼び出します。
Value価値型の起源
.NETの構造は、一方では古典的な言葉の意味(レイアウト、可変性など)での構造であり、他方では原則としてOOPおよび.NET環境(ToString、GetHashCodeメソッド、System.ValueTypeからの継承)をサポートします。 System.Objectから順に;など)。
構造が他の型から継承できない理由をよりよく理解するには、CLRのメソッドの組織レベルに移動する必要があります。
インスタンスレベルのメソッドには、これに対する暗黙の引数があります。 実際、それは明示的です。 JITは、コードをコンパイルして、次の形式の署名を作成します。
ReturnType MethodName(Type this, …arguments…)
ただし、これは参照型用です。
重要な場合:
ReturnType MethodName(ref Type this, …arguments…)
はい、はい! これは、構造の可変性をサポートするために行われます。 これを修正できるように。
では、構造が他の型から継承できないのはなぜですか?
質問に答えます:それが基本参照クラスの仮想メソッドである場合はどうでしょうか? JITコンパイラになるには? まさか。 仮想メソッドテーブルをスケジュールする以外に、さまざまなコードの特殊化(byvalおよびbyrefセマンティクスを使用)を常に推測して生成することは非効率的です。 仮想メソッドを適切に提供するために、ボクシングが追加されました。
しかし... ToString 、 GetHashCode 、 Equalsメソッドは、 System.Object祖先参照クラスの仮想メソッドですか?!
これらは例外です。 JITはこれを認識しており、これらのメソッドに対してのみバインディングと特殊化を生成します。
boundバインドされていないデリゲート
.NETのリフレクションにより、静的メソッドとインスタンスの両方のデリゲートを作成できます。
ただし、小さな問題があります-インスタンスの場合、新しい方法でデリゲートを作成する必要があります。
例を考えてみましょう:
class Program { static void Main(string[] args) { var calc = new Calc() { FirstOperand = 2 }; var addMethodInfo = typeof(Calc).GetMethod("Add", BindingFlags.Public | BindingFlags.Instance); var addDelegate = (Func<int, int>)Delegate.CreateDelegate( typeof(Func<int, int>), calc, addMethodInfo); Console.WriteLine(addDelegate(2)); // 4 } } class Calc { public int FirstOperand = 0; public int Add(int secondOperand) { return FirstOperand + secondOperand; } }
バインドされていないデリゲートは、 救助に来ます、すなわち 繋がれていません。 ただし、1つの機能があります。最初の引数が追加される別の署名(はい、あなたは正しく推測しました)-インスタンスへのリンク。
つまり バインドされていないデリゲート-これらは「実際の」メソッドへのリンクです。
そのため、シグネチャAdd(int secondOperand)はAdd( Calc this 、int secondOperand)になります。
チェック:
class Program { static void Main(string[] args) { var addMethodInfo = typeof(Calc).GetMethod("Add", BindingFlags.Public | BindingFlags.Instance); var addDelegate = (Func<Calc, int, int>)Delegate.CreateDelegate( typeof(Func<Calc, int, int>), null, addMethodInfo); Console.WriteLine(addDelegate(new Calc(), 2)); // 2 } } class Calc { public int FirstOperand = 0; public int Add(int secondOperand) { return FirstOperand + secondOperand; } }
構造メソッドのシグネチャに関する質問を覚えていますか? Calc型を構造体として宣言して実行します。 ArgumentException? え?
このbyref引数をFunc <Calc、int、int>に渡す必要がありますが、どのように?!
デリゲートFuncByRefを宣言します
delegate TResult FuncByRef<T1, in T2, out TResult>(ref T1 arg1, T2 arg2);
コードを変更します。
class Program { delegate TResult FuncByRef<T1, in T2, out TResult>(ref T1 arg1, T2 arg2); static void Main(string[] args) { var addMethodInfo = typeof(Calc).GetMethod("Add", BindingFlags.Public | BindingFlags.Instance); var addDelegate = (FuncByRef<Calc, int, int>)Delegate.CreateDelegate( typeof(FuncByRef<Calc, int, int>), null, addMethodInfo); var calc = new Calc(); calc.FirstOperand = 123; Console.WriteLine(addDelegate(ref calc, 2)); // 125 } } struct Calc { public int FirstOperand; public int Add(int secondOperand) { return FirstOperand + secondOperand; } }
▌検証回避
簡単なアプリケーションを考えてみましょう。
class Program { static void Main(string[] args) { CallTest(new object()); CallTestWithExlicitCasting(new object()); Console.Read(); } static void CallTest(object target) { Program p = target as Program; p.Test(); } static void CallTestWithExlicitCasting(object target) { Program p = (Program)target; p.Test(); } public void Test() { Console.WriteLine("Test"); } }
ご覧のとおり、CallTest()を呼び出すと、アプリケーションはNullReferenceExceptionでクラッシュします。
さて、この状況を修正しましょう。 これを行うには、 ildasmを実行します 。
Visual Studio Command Promt -> ildasm
次の
File -> Dump -> Save as dialog -> msiltricks_patch.il
保存したmsiltricks_patch.ilファイルをお気に入りのエディターで開き、CallTestメソッドの本体を見つけます。
.method private hidebysig static void CallTest(object target) cil managed { // Code size 14 (0xe) .maxstack 1 .locals init ([0] class MSILTricks.Program p) IL_0000: ldarg.0 IL_0001: isinst MSILTricks.Program IL_0006: stloc.0 IL_0007: ldloc.0 IL_0008: callvirt instance void MSILTricks.Program::Test() IL_000d: ret } // end of method Program::CallTest
用語IL_0001を削除します:isinst MSILTricks.Program、つまり opコードisinstの呼び出し (C#の演算子としても知られています)。
CallTestWithExlicitCastingメソッドでも同じことを行います。
.method private hidebysig static void CallTestWithExlicitCasting(object target) cil managed { // Code size 14 (0xe) .maxstack 1 .locals init ([0] class MSILTricks.Program p) IL_0000: ldarg.0 IL_0001: castclass MSILTricks.Program IL_0006: stloc.0 IL_0007: ldloc.0 IL_0008: callvirt instance void MSILTricks.Program::Test() IL_000d: ret } // end of method Program::CallTestWithExlicitCasting
削除用語
IL_0001: castclass MSILTricks.Program
、つまり castclassオペコードの呼び出し(別名C#の明示的なキャスト演算子)。
Visual Studio Command Promt -> cd [your saved file dir]
Visual Studio Command Promt -> ilasm msiltricks_patch.il
msiltricks_patch.exeを実行します
単一の例外ではなく、 AccessViolationExceptionでもありません。
ハハ
実際、 Testメソッドには副作用がなく、 これを本体で使用していません。
結論:私たちはハードウェアで作業しており、参照型の変数は単にメモリ内のアドレスです。 DWORD 型変換など コンパイル段階での抽象化と「保護」にすぎません。 中央処理装置は、メモリ内のアドレスを処理します。 CLRはこれらのアドレスを提供し、JITはそれらを考慮してコードをコンパイルします。
あなたのKO :)
そして、はい、 callvirt命令はオブジェクトの「正確さ」をチェックしません。
AccessViolationExceptionを取得するには、たとえば、仮想メソッドをProgramクラスに追加し、Testメソッドで呼び出すことができます。