C#.Net-Cribまたは要点の簡単な説明でのデリゲートとラムダ式

こんにちは親愛なる読者!



.Netで少しでも作業を行ったほとんどの人は、デリゲートが何であるかを知っています。 そして、それらについて知らない人は、ほぼ間違いなく、ラムダ式を知っています。 しかし、個人的には、宣言の構文を常に忘れてから、コンパイラがそのような構造にどのように反応するかについて、賢い人々の複数ページの説明に戻ります。 このような問題がある場合は、大歓迎です!



代表者



デリゲートは特別なタイプです。 そして彼は特別な方法で宣言されています:



delegate int MyDelegate (string x);
      
      





ここではすべてが簡単です。 デリゲートキーワードがあり、次にデリゲート自体がMyDelegateという名前で、intと文字列型の1つの引数によって返されます。



実際、CILでコードをコンパイルするとき、コンパイラはそのような各デリゲート型を同じ名前のクラス型に変換し、このデリゲート型のすべてのインスタンスは実際には対応するクラス型のインスタンスです。 このような各クラスは、CombineおよびRemoveメソッドを取得するMulticastDelegate型を継承し、2つの引数target (Object)およびmethodPtr (IntPtr)、 invocationList (Object)フィールド、および3つの独自メソッドInvoke、BeginInvoke、EndEnvokeを持つコンストラクターを含みます。



新しいデリゲート型を宣言するとき、その宣言の構文を通して、そのようなデリゲートのインスタンスを初期化できる有効なメソッドのシグネチャを厳密に決定します。 これはすぐに、Invoke、BeginInvoke、EndEnvokeの自動生成メソッドのシグネチャに影響を与えるため、これらのメソッドはベースタイプから継承されず、各デリゲートタイプに対して個別に定義されます。



このようなデリゲートのインスタンスは、特定のメソッドまたはメソッドのリストへリンクとして理解される必要があります。 リンクはどこかで渡され、ほとんどの場合、すでに反対側で実行されています。 さらに、クライアントは、メソッドを使用して、実行に使用する引数の値を渡すことはできません(許可しない限り)。また、署名を変更することもできません。 しかし、彼はメソッドのロジック、つまり彼の体を決定することができます。



実行時にデリゲートに渡す引数のタイプと、デリゲートから返される戻り値のタイプがわかっているため、これはコードにとって便利で安全です。



空想する場合、デリゲートの引数をクライアント側に渡す権利を提供できます。たとえば、引数デリゲートと、メソッド内でこのデリゲートに渡される引数を持つメソッドを作成します。これにより、クライアントはデリゲートのメソッドの引数値も設定できます。 たとえば、このように。



 void MyFunc(myDelegate deleg, int arg){deleg.Invoke(arg);}
      
      





コードでデリゲートインスタンスを作成すると、メソッドがコンストラクタに渡されます(インスタンスと静的の両方が適切です。主なことは、メソッドの署名がデリゲートの署名と一致することです)。 メソッドがインスタンスの場合、メソッドのインスタンス所有者への参照はターゲットフィールドに書き込まれます(メソッドがインスタンスである場合、これは少なくともこのターゲットのフィールドを操作することを意味するため、これが必要です)、およびmethodPtrでは 、メソッドへの参照です。 メソッドが静的である場合、nullおよびメソッドへの参照は、それぞれtargetおよびmethodPtrフィールドに書き込まれます。



デリゲートインスタンスを作成して、デリゲート変数を初期化できます。



 MyDeleg x = new MyDeleg(MyFunc);
      
      





または、コンストラクターを呼び出さない単純な構文:



 MyDeleg x = MyFunc;
      
      





デリゲートインスタンスの転送/受信を調整するには、いくつかの方法があります。 デリゲートは最終的に単なる型クラスであるため、フィールド、プロパティ、メソッド引数などを自由に作成できます。 特定の種類のデリゲート。



デリゲートメソッド:



呼び出し -デリゲートに格納されているメソッドの同期実行。

BeginInvoke、EndEnvoke-似ていますが非同期です。



簡略化された構文を使用して、デリゲートに格納されているメソッドの実行を呼び出すことができます。



 delegInst.Invoke(argument);
      
      





これは書くことに似ています:



 delegInst(argument);
      
      





デリゲートにinvocationListフィールドが必要なのはなぜですか?



デリゲートが1つのメソッドへの参照を保持している間、デリゲートインスタンスのinvocationListフィールドはnullです。 このメソッドは、「=」変数を介してデリゲートの新しいインスタンスを同等化することにより、別のメソッドにいつでも書き直すことができます(または、簡略化された構文ですぐに必要なメソッド)。 ただし、デリゲートが複数のメソッドへのリンクを保存するときに呼び出しのチェーンを作成することもできます。

これを行うには、 Combineメソッドを呼び出します。



 MyDeleg first = MyFunc1; MyDeleg second = MyFunc2; first = (MyDeleg) Delegate.Combine(first, second);
      
      





Combineメソッドは、ターゲットおよびmethodPtrフィールドが空である新しいデリゲートへのリンクを返しますが、2つのデリゲート参照を含むinvocationList:以前は最初の変数にあり、2番目にまだ格納されているもの。 Combineメソッドを使用して3番目のデリゲートを追加し、その結果を最初に書き込むと、メソッドは3つのリンクのコレクションがあるinvocationListフィールドを持つ新しいデリゲートへのリンクを返し、2つのリンクを持つデリゲートは次のクリーニングサイクルでガベージコレクターによって削除されることを理解する必要があります。



このようなデリゲートを実行すると、すべてのメソッドが順番に実行されます。 デリゲート署名にパラメーターの受信が含まれる場合、パラメーターはすべてのメソッドで同じ値になります。 戻り値がある場合、リストの最後のメソッドの値のみを取得できます。



Removeメソッドは、所有者オブジェクトの値とメソッドでデリゲートのリストを検索し、見つかった場合、一致する最初のデリゲートを削除します。



 Deleg first = first.Remove(MyFunc2);
      
      





デリゲートのオーバーライド+ =および-=は、CombineおよびRemoveメソッドに類似しています。



 first = (Deleg) Delegate.Combine(first, second);
      
      





次のエントリに似ています:



 first += MyFunc2;
      
      





それに応じて:



 first = first.Remove(MyFunc2);
      
      





次のエントリに似ています:



 first -= MyFunc;
      
      





デリゲートは一般化 (ジェネリック)できることを言っておく価値があります。これは、タイプごとに個別のデリゲートを作成するためのより適切なアプローチです。



また、FCLライブラリには、最も一般的な種類のデリゲート(一般化されたものとそうでないもの)が既に含まれていることにも言及する価値があります。 たとえば、 アクション<T>デリゲートは、戻り値はあるが引数はあるメソッドであり、 Fucn <T、TResult>は戻り値と引数があるメソッドです。



ラムダ演算子とラムダ式



lambda-operatorまたはlambda-expressionを使用してデリゲートインスタンスを初期化することもできます。 一般にこれはまったく同じであるため、本文では、違いを強調する必要がない場所で単に「ラムダ」と呼びます。

C#3.0で導入されたことに言及する価値があり、それらの前にC#2.0で登場した匿名関数がありました。



ラムダの顕著な特徴は演算子=>であり、これは式をパラメーターを持つ左側とメソッド本体を持つ右側に分割します



デリゲートがあるとしましょう:



 delegate string MyDeleg (string verb);
      
      





その場合、ラムダ演算子の一般的な構文は次のようになります。



 MyDeleg myDeleg = (string x) => { return x; };
      
      





本体を中括弧で囲んでいるため、これはまさにLambda演算子です 。これにより、複数の演算子を挿入できます。



 MyDeleg myDeleg = (string x) => { var z = x + x; return z; };
      
      





コンパイラーはデリゲートのタイプと署名を既に知っているため、引数のタイプを指定しないことが許可されていますが、他の人がコードを読みやすいように指定することもできます。



 MyDeleg myDeleg = (x) => { return x; };
      
      





引数が1つしかない場合は、引数を囲む括弧を省略できます。



 MyDeleg myDeleg = x => { return x; };
      
      





デリゲートシグネチャに引数がない場合は、空の角かっこを指定する必要があります。



 AnotherDeleg myDeleg = () => { return x; };
      
      







ラムダの本体が1つの式のみで構成されている場合、 ラムダ式です。 次のような単純化された構文を使用する機会があるため、これは非常に便利です。



-ラムダの本体を囲む中括弧を省略できます。



-上記の中括弧がなければ、ステートメントの前にreturnキーワードを使用し、ラムダの本体でステートメントの後にセミコロンを使用する必要はありません。



その結果、ラムダ定義コードは小さくなります。

 MyDeleg myDeleg = x => x+x;
      
      







コンパイラはラムダについてどう思いますか?



ラムダ式はデリゲートに直接渡される魔法の文字列ではないことを理解することが重要です。 実際、コンパイル段階では、このような各式は、名前が「<」で始まる匿名のプライベートメソッドに変わり、そのようなメソッドを直接呼び出すことはできません。 このメソッドは、常に特定のラムダ式を使用する型のメンバーであり 、CILコードで明示的にデリゲートコンストラクターに渡されます。



さらに、コンパイラは、本体の式に、式が更新されるタイプのインスタンスフィールドを持つ操作が含まれているかどうかを分析します。 その場合、生成されたメソッドはインスタンスになり、そうでない場合、メソッドは静的になります。 ラムダ式での特定の型の静的フィールド、および他の型のインスタンスおよびインスタンスフィールドの使用は、これに影響しません。



どちらの場合もCLRがインスタンスメソッドを生成しない理由を尋ねるかもしれません。答えは簡単です。このメソッドには追加のthisパラメーターが必要で、静的よりも実行に時間がかかります。



さらに、CLRは、初めてアクセスされたときに匿名プライベートフィールド(ラムダ式が使用された型ではすべて同じ)でメソッドを使用しデリゲートをキャッシュする構造を作成し、次のフィールドではフィールドから読み取ります。 実際、式で指定されたメソッドに関する情報はプログラムの実行段階で変更されないため、毎回新しく作成する意味はありません。



最後に



しかし、最終的に私は全体の夜を過ごしました...フー! 私はベビーベッドを最もコンパクトで有益なものにしようとしましたが、それでも何とかたくさんの手紙が出てきました。 事前のコメントをありがとう、私はすぐにすべての欠陥を修正しようとします。



もちろん、この記事は読みませんが、私が何度も何度も読み直したすばらしい本「CLR via C#」と、このチートシートを書くのに使った情報を書いてくれた素晴らしいJeffrey Richterに感謝します。



どうもありがとうございました!



All Articles