web-dev-qa-db-ja.com

匿名メソッドとラムダ式

匿名メソッドとラムダ式を簡潔に区別できる人はいますか?

匿名メソッドの使用法:

private void DoSomeWork()
{
    if (textBox1.InvokeRequired)
    {
        //textBox1.Invoke((Action)(() => textBox1.Text = "test"));
        textBox1.Invoke((Action)delegate { textBox1.Text = "test"; });
    }
}

強く型付けされたデリゲートにキャストされるのは通常のラムダ式だけですか、それともそれ以上のものが隠されていますか?.

フォローのような強く型付けされたデリゲートをよく知っています

UpdateTextDelegate mydelegate = new UpdateTextDelegate(MethodName)

タイプSystem.Delegateのパラメーターとしては十分ですが、匿名メソッドのアイデアは私にはかなり新しいものです。

19
sarepta

is匿名メソッド?本当に匿名ですか?名前はありますか?すべての良い質問なので、それらから始めて、ラムダ式に向かって進んでいきましょう。

これを行うとき:

_public void TestSomething()
{
    Test(delegate { Debug.WriteLine("Test"); });
}
_

実際に何が起こりますか?

コンパイラーは最初に、メソッドの「本体」を取得することを決定します。これは次のとおりです。

_Debug.WriteLine("Test");
_

それをメソッドに分けます。

コンパイラが答えなければならない2つの質問:

  1. メソッドはどこに置くべきですか?
  2. メソッドのシグネチャはどのようになりますか?

2番目の質問は簡単に答えられます。 _delegate {_の部分がそれに答えます。このメソッドはパラメーターを取りません(delegateと_{_の間に何もありません)。名前は気にしないので(したがって「匿名」部分)、メソッドを次のように宣言できます。

_public void SomeOddMethod()
{
    Debug.WriteLine("Test");
}
_

しかし、なぜそれがこれをすべて行ったのですか?

Actionなどのデリゲートが実際に何であるかを見てみましょう。

デリゲートとは、.NETのデリゲートが実際にリンクされているという事実を少しの間無視すると、複数単一の「デリゲート」、2つのものへの参照(ポインタ)です。

  1. オブジェクトインスタンス
  2. そのオブジェクトインスタンスのメソッド

したがって、その知識があれば、最初のコードは実際には次のように書き直すことができます。

_public void TestSomething()
{
    Test(new Action(this.SomeOddMethod));
}

private void SomeOddMethod()
{
    Debug.WriteLine("Test");
}
_

さて、これに関する問題は、コンパイラが与えられたデリゲートで実際にTestが何をするかを知る方法がなく、デリゲートの半分がメソッドの対象となるインスタンスへの参照であるためです。上記の例ではthisと呼ばれますが、参照されるデータの量はわかりません。

たとえば、上記のコードが本当に巨大なオブジェクトの一部であるが、一時的にしか存在しないオブジェクトであるかどうかを考えてみてください。また、Testは、そのデリゲートを長期間存続する場所に格納することも考慮してください。その「長い時間」は、その巨大なオブジェクトの寿命にも結びつき、それへの参照も長い間維持しますが、おそらく良くありません。

したがって、コンパイラはメソッドを作成するだけでなく、それを保持するクラスも作成します。これは最初の質問に答えますどこに置くべきですか?

したがって、上記のコードは次のように書き直すことができます。

_public void TestSomething()
{
    var temp = new SomeClass;
    Test(new Action(temp.SomeOddMethod));
}

private class SomeClass
{
    private void SomeOddMethod()
    {
        Debug.WriteLine("Test");
    }
}
_

つまり、この例では、匿名メソッドが実際に何であるかを示しています。

ローカル変数を使い始めると、状況は少し厄介になります。次の例を検討してください。

_public void Test()
{
    int x = 10;
    Test(delegate { Debug.WriteLine("x=" + x); });
}
_

これは、ボンネットの下で起こること、または少なくともそれに非常に近い何かです。

_public void TestSomething()
{
    var temp = new SomeClass;
    temp.x = 10;
    Test(new Action(temp.SomeOddMethod));
}

private class SomeClass
{
    public int x;

    private void SomeOddMethod()
    {
        Debug.WriteLine("x=" + x);
    }
}
_

コンパイラはクラスを作成し、メソッドが必要とするすべての変数をそのクラスに持ち上げ、ローカル変数へのすべてのアクセスを匿名型のフィールドへのアクセスに書き換えます。

クラスの名前とメソッドは少し奇妙です。質問してみましょう LINQPad それは何でしょうか:

_void Main()
{
    int x = 10;
    Test(delegate { Debug.WriteLine("x=" + x); });
}

public void Test(Action action)
{
    action();
}
_

LINQPadにこのプログラムのIL(中間言語)を出力するように依頼すると、次のようになります。

_// var temp = new UserQuery+<>c__DisplayClass1();
IL_0000:  newobj      UserQuery+<>c__DisplayClass1..ctor
IL_0005:  stloc.0     // CS$<>8__locals2
IL_0006:  ldloc.0     // CS$<>8__locals2

// temp.x = 10;
IL_0007:  ldc.i4.s    0A 
IL_0009:  stfld       UserQuery+<>c__DisplayClass1.x

// var action = new Action(temp.<Main>b__0);
IL_000E:  ldarg.0     
IL_000F:  ldloc.0     // CS$<>8__locals2
IL_0010:  ldftn       UserQuery+<>c__DisplayClass1.<Main>b__0
IL_0016:  newobj      System.Action..ctor

// Test(action);
IL_001B:  call        UserQuery.Test

Test:
IL_0000:  ldarg.1     
IL_0001:  callvirt    System.Action.Invoke
IL_0006:  ret         

<>c__DisplayClass1.<Main>b__0:
IL_0000:  ldstr       "x="
IL_0005:  ldarg.0     
IL_0006:  ldfld       UserQuery+<>c__DisplayClass1.x
IL_000B:  box         System.Int32
IL_0010:  call        System.String.Concat
IL_0015:  call        System.Diagnostics.Debug.WriteLine
IL_001A:  ret         

<>c__DisplayClass1..ctor:
IL_0000:  ldarg.0     
IL_0001:  call        System.Object..ctor
IL_0006:  ret         
_

ここでは、クラスの名前が_UserQuery+<>c__DisplayClass1_であり、メソッドの名前が_<Main>b__0_であることがわかります。このコードを生成したC#コードで編集しましたが、LINQPadは上記の例のIL以外は何も生成しません。

小なり記号と大なり記号は、コンパイラが生成したものと一致する型やメソッドを誤って作成できないようにするためのものです。

つまり、これが基本的に匿名メソッドです。

それで、これは何ですか?

_Test(() => Debug.WriteLine("Test"));
_

まあ、この場合も同じです。匿名メソッドを作成するためのショートカットです。

これは2つの方法で書くことができます:

_() => { ... code here ... }
() => ... single expression here ...
_

最初の形式では、通常のメソッド本体で行うすべてのコードを記述できます。 2番目の形式では、1つの式またはステートメントを記述できます。

ただし、この場合、コンパイラはこれを処理します。

_() => ...
_

これと同じ方法:

_delegate { ... }
_

それらはまだ匿名のメソッドであり、_() =>_構文がそれに到達するためのショートカットであるというだけです。

それで、それがそれに到達するための近道であるならば、なぜ私たちはそれを持っているのですか?

さて、それはそれが追加された目的、つまりLINQの生活を少し楽にします。

このLINQステートメントを検討してください。

_var customers = from customer in db.Customers
                where customer.Name == "ACME"
                select customer.Address;
_

このコードは次のように書き直されます。

_var customers =
    db.Customers
      .Where(customer => customer.Name == "ACME")
      .Select(customer => customer.Address");
_

_delegate { ... }_構文を使用する場合は、式を_return ..._などで書き直す必要があり、よりファンキーに見えます。したがって、ラムダ構文が追加され、上記のようなコードを作成する際のプログラマーの作業が楽になりました。

では、式とは何ですか?

これまで、Testがどのように定義されているかは示していませんが、上記のコードに対してTestを定義しましょう。

_public void Test(Action action)
_

これで十分です。 「デリゲートが必要です。アクションタイプです(パラメーターを受け取らず、値を返さない)」と書かれています。

ただし、Microsoftは、このメソッドを定義する別の方法も追加しました。

_public void Test(Expression<Func<....>> expr)
_

そこに_...._パーツをドロップしたことに注意してください。それに戻りましょう。 1

このコードとこの呼び出しの組み合わせ:

_Test(() => x + 10);
_

実際にはデリゲートを渡さず、(すぐに)呼び出すことができるものも渡されません。代わりに、コンパイラはこのコードを次のコードに書き換えますsimilar(ただし、まったく同じではありません)。

_var operand1 = new VariableReferenceOperand("x");
var operand2 = new ConstantOperand(10);
var expression = new AdditionOperator(operand1, operand2);
Test(expression);
_

基本的に、コンパイラは、変数、リテラル値、使用される演算子などへの参照を含む_Expression<Func<...>>_オブジェクトを構築し、そのオブジェクトツリーをメソッドに渡します。

どうして?

さて、上記のdb.Customers.Where(...)の部分を考えてみましょう。

すべての顧客(およびすべてのデータ)をデータベースからクライアントにダウンロードする代わりに、それらすべてをループして、どの顧客が正しい名前を持っているかを見つけるなど、コードが実際にデータベースに要求するのであれば、それは素晴らしいことではないでしょうか。その単一の正しい顧客を一度に見つけますか?

それが表現の背後にある目的です。 Entity Framework、Linq2SQL、またはその他のLINQをサポートするデータベースレイヤーは、その式を取得して分析し、分解して、データベースに対して実行される適切な形式のSQLを作成します。

これは、ILを含むメソッドへのデリゲートをまだ与えている場合はnever実行できます。これができるのは、いくつかの理由だけです。

  1. _Expression<Func<...>>_に適したラムダ式で許可される構文は制限されています(ステートメントなどはありません)
  2. 中括弧のないラムダ構文。これは、これがより単純な形式のコードであることをコンパイラーに通知します。

それで、要約しましょう:

  1. 匿名メソッドは実際にはそれほど匿名ではなく、名前付きタイプになり、名前付きメソッドのみが含まれます自分で名前を付ける必要はありません
  2. あなたがする必要がないように物事を動かすのは内部でたくさんのコンパイラの魔法です
  3. 式とデリゲートは、同じもののいくつかを見る2つの方法です
  4. 式は、知りたいフレームワークを対象としていますコードの機能と方法その知識を使用してプロセスを最適化できるようにします(SQLステートメントの記述など)。
  5. デリゲートは、メソッドを呼び出すことができるのみを懸念するフレームワークを対象としています。

脚注:

  1. このような単純な式の_...._部分は、式から取得する戻り値のタイプを対象としています。 _() => ... simple expression ..._は、expressions、つまり値を返すもののみを許可し、複数のステートメントにすることはできません。したがって、有効な式の型は次のとおりです。_Expression<Func<int>>_、基本的に、式は整数値を返す関数(メソッド)です。

    「値を返す式」は_Expression<...>_パラメーターまたはタイプの制限であり、デリゲートの制限ではないことに注意してください。 TestのパラメータタイプがActionの場合、これは完全に正当なコードです。

    _Test(() => Debug.WriteLine("Test"));
    _

    明らかに、Debug.WriteLine("Test")は何も返しませんが、これは合法です。ただし、メソッドTestexpressionが必要な場合、式は値を返す必要があるため、そうではありません。

注意すべき微妙な違いが1つあります。次のクエリを検討してください(ことわざのNorthWindを使用)。

Customers.Where(delegate(Customers c) { return c.City == "London";});
Customers.Where(c => c.City == "London");

1つ目は匿名デリゲートを使用し、2つ目はラムダ式を使用します。両方の結果を評価すると、同じことがわかります。ただし、生成されたSQLを見ると、まったく別の話がわかります。最初の生成

SELECT [t0].[CustomerID], [t0].[CompanyName], [t0].[ContactName], [t0].[ContactTitle], [t0].[Address], [t0].[City], [t0].[Region], [t0].[PostalCode], [t0].[Country], [t0].[Phone], [t0].[Fax]
FROM [Customers] AS [t0]

一方、2番目は生成します

SELECT [t0].[CustomerID], [t0].[CompanyName], [t0].[ContactName], [t0].[ContactTitle], [t0].[Address], [t0].[City], [t0].[Region], [t0].[PostalCode], [t0].[Country], [t0].[Phone], [t0].[Fax]
FROM [Customers] AS [t0]
WHERE [t0].[City] = @p0

最初のケースでは、where句がデータベースに渡されていないことに注意してください。どうしてこれなの?コンパイラは、ラムダ式が式ツリーとして保持できる単純な1行の式であると判断できますが、匿名デリゲートはラムダ式ではないため、Expression<Func<T>>としてラップできません。結果として、最初のケースでは、Where拡張メソッドに最適なのは、Expression<Func<T, bool>>を必要とするIQueryableバージョンではなくIEnumerableを拡張するメソッドです。

この時点では、匿名のデリゲートはほとんど役に立ちません。より冗長で柔軟性が低くなります。一般に、匿名デリゲート構文よりもラムダ構文を常に使用し、構文の簡潔さも理解することをお勧めします。

5
Jim Wooley

正確には、「匿名デリゲート」という名前は実際には匿名メソッドです。

まあ、ラムダと匿名メソッドはどちらも単なるシンタックスシュガーです。コンパイラーは少なくとも「通常の」メソッドを生成しますが、場合によっては(クロージャの場合)、匿名ではなくなったメソッドを含むネストされたクラスが生成されます。

1
BayStallion