興味深いC#知識と.NETメカニズム

C#と.NET全般に関する多くの質問を提供します。これらは、インタビューや.NETプラットフォームの仕組みをよりよく理解するのに役立ちます。 参照型が重要な型などとどのように異なるかについて、通常の質問はありません。 検討に値する最も興味深いものを選択しようとしました。



  1. ヒープに参照型のオブジェクトを配置すると、型のオブジェクト(静的フィールドを含むメモリ領域と静的メソッドの実装)へのポインタを持つことが知られています。 この型オブジェクトには、同期ブロックのインデックスと型オブジェクトへの別のポインターが含まれます。 なぜ必要なのか、それはどこに示されているのか?



    答え
    CLRでは、ヒープ上のすべてのオブジェクトに型オブジェクトへのポインターがあります。 これは、たとえば、静的フィールドの値や型のインスタンスの静的メソッドの実装を見つけるために必要です。 ただし、型インスタンスによって参照される型オブジェクトには型オブジェクトへの参照もあり、CLRが起動時に作成する型オブジェクトであるSystem.Type型オブジェクトの「インスタンス」です。



    この図では、 Managerオブジェクトは、型オブジェクトへのポインターがSystem.Type型オブジェクトを参照する型のManagerオブジェクトを参照しています



  2. クラス内だけでなく、グローバルスコープでもデリゲートを宣言することは可能ですか? なんで?



    答え
    できます。 デリゲートは、メソッドの単なるラッパーではなく、本格的なクラスであり、クラスは親クラスでネストするか、単にグローバルスコープで宣言することができます。 つまり、デリゲートは、クラスを定義できる場所であればどこでも定義できます。



    internal class Feedback : System.MulticastDelegate { //  public Feedback(Object object, IntPtr method); // ,       public virtual void Invoke(Int32 value); // ,     public virtual IAsyncResult BeginInvoke(Int32 value, AsyncCallback callback, Object object); public virtual void EndInvoke(IAsyncResult result); }
          
          





    もう1つの興味深い質問は、デリゲートクラスコンストラクターに2つのパラメーターが含まれていることです。コードでは、ポインターをメソッド(このメソッドを見つけるCLRの内部)に渡すだけですか。



     delegate void Test(int value); void A(int v) { Console.WriteLine(v); } void TestDelegate() { var t = new Test(A); t(1); }
          
          





    すべてが単純です-デリゲートを作成するときに、コンパイラがコンストラクタ内のオブジェクトパラメータの値を置き換えるためです。 デリゲートを初期化するメソッドが静的な場合、nullが渡されます。 それ以外の場合、メソッドが属するクラスインスタンスのオブジェクトが渡されます。 この場合、このオブジェクトの状態は、メソッド内のthisキーワードを介して変更できます。



  3. 簡単な質問は、Testメソッドが表示するものとその理由です。



     delegate int GetValue(); int Value1() { return 1; } int Value2() { return 2; } void Test() { var v1 = new GetValue(Value1); var v2 = new GetValue(Value2); var chain = v1; chain += v2; Console.WriteLine(chain()); }
          
          





    答え
    出力2.デリゲートをチェーンに配置すると、デリゲートチェーンは、デリゲートの配列である内部フィールドを埋めます(数が複数の場合、メソッド参照は単に格納されます)。 すべてのデリゲートは順番に実行されます。 後者の値が返され、残りは考慮されません。



  4. WaitCallbackがパラメーターを1つしか受け入れない場合(この場合、その参照はnull )、 Testメソッドからのローカル変数pass1およびpass2がどのようにラムダ式に渡されるかを説明します。



     namespace ConsoleApplication1 { class Program { static void Main(string[] args) { var p = new Program(); p.Test(); Console.ReadKey(); } void Test() { int pass1 = 5; object pass2 = "Passing test"; ThreadPool.QueueUserWorkItem((obj) => { Console.WriteLine(pass1); Console.WriteLine(pass2); }); } } }
          
          





    答え
    これを理解するには、ildasmでアセンブリを開きます。

    この場合、ラムダ式がメソッドではなく、クラス全体であることを確認できます!



     .method private hidebysig instance void Test() cil managed { //  : 44 (0x2c) .maxstack 2 .locals init ([0] class ConsoleApplication1.Program/'<>c__DisplayClass1_0' 'CS$<>8__locals0') IL_0000: newobj instance void ConsoleApplication1.Program/'<>c__DisplayClass1_0'::.ctor() IL_0005: stloc.0 IL_0006: nop IL_0007: ldloc.0 IL_0008: ldc.i4.5 IL_0009: stfld int32 ConsoleApplication1.Program/'<>c__DisplayClass1_0'::pass1 IL_000e: ldloc.0 IL_000f: ldstr "Passing test" IL_0014: stfld object ConsoleApplication1.Program/'<>c__DisplayClass1_0'::pass2 IL_0019: ldloc.0 //    ! IL_001a: ldftn instance void ConsoleApplication1.Program/'<>c__DisplayClass1_0'::'<Test>b__0'(object) IL_0020: newobj instance void [mscorlib]System.Threading.WaitCallback::.ctor(object, native int) IL_0025: call bool [mscorlib]System.Threading.ThreadPool::QueueUserWorkItem(class [mscorlib]System.Threading.WaitCallback) IL_002a: pop IL_002b: ret } // end of method Program::Test
          
          





    そして、これはクラス自体の説明であり、議論されたメソッドが含まれています:



    コンパイラは、ラムダ式にローカル変数への参照が含まれているかどうかを判断します。 そうでない場合、静的メソッドが生成されます(ラムダ式に型インスタンスのメンバーへの参照がある場合はインスタンスメソッド)。 ローカル変数への参照が存在する場合、必要なフィールドとラムダ式で記述されたメソッドを含むクラスが生成されます。



  5. 次のコードは何を表示しますか?



     int a = -5; Console.WriteLine(~a);
          
          





    答え
    印刷4.〜演算子はビットごとの反転を実行します。



     Console.WriteLine("{0:x8}, {1:x8}", -5, ~(-5)); //  fffffffb, 00000004
          
          





    値5の場合、-6が出力されます。



  6. 通常、手動のガベージコレクションは推奨されません。 なんで? GC.Collect()メソッドを呼び出すことが理にかなっている場合の例を挙げてください。



    答え
    事実は、ガベージコレクター自体が世代のしきい値を設定することです(アプリケーションの実際の動作に依存します)。 管理ヒープの世代サイズがしきい値を超えるとすぐに、ガベージコレクションが開始されます(これについては、リヒターで詳しく説明されています)。 したがって、ほとんどの場合、GC.Collect()の呼び出しを避ける必要があります。 ただし、1回限りのイベントが発生して多くの古いオブジェクトが破壊された場合は、手動のガベージコレクションが必要になる場合があります 。 したがって、アプリケーションの過去の動作に基づく予測は正確ではなく、ガベージコレクションは非常に役立ちます。



  7. インタビューボーナス:同じ確率で0または1を返すrand2メソッドがあります。 同じ確率で0,1,2を生成するrand2メソッドを使用してrand3メソッドを記述します。



    答え
     //   int rand3() { int x, y; do { x = rand2(); y = rand2(); } while (x == 0 && y == 1); return x + y; } //   int rand3() { int r = 2 * rand2() + rand2(); if (r < 3) return r; return rand3(); }
          
          







どんな批判も歓迎します。 質問があれば、まだ他のトピックについてです。



All Articles