.NET Frameworkのラムダとデリゲートの違いは何ですか?


86

私はこの質問をよく聞かれ、違いを最もよく説明する方法についていくつかの意見を求めたいと思いました。


2
「デリゲート」とは、デリゲートタイプ、または匿名デリゲートを意味しますか?それらも異なります。
Chris Ammerman


1
なぜ人々は彼の質問をとても複雑にするのですか?デリゲートとは何か、ラムダとは何かに答えるだけです。できるだけ多くの説明をして、彼に適切なものを選ばせてください。
ImirHoxha19年

回答:


96

それらは実際には2つの非常に異なるものです。「デリゲート」は、実際にはメソッドまたはラムダへの参照を保持する変数の名前であり、ラムダは永続的な名前のないメソッドです。

ラムダは、いくつかの微妙な違いを除いて、他の方法と非常によく似ています。

  1. 通常のメソッドは「ステートメント」で定義され、永続的な名前に関連付けられますが、ラムダは「式」オンザフライ」で定義され、永続的な名前はありません。
  2. 一部のラムダは.NET式ツリーで使用できますが、メソッドは使用できません。

デリゲートは次のように定義されます。

delegate Int32 BinaryIntOp(Int32 x, Int32 y);

BinaryIntOp型の変数には、シグネチャが同じである限り、メソッドまたはlabmdaのいずれかを割り当てることができます。2つのInt32引数とInt32リターンです。

ラムダは次のように定義できます。

BinaryIntOp sumOfSquares = (a, b) => a*a + b*b;

もう1つの注意点は、一般的なFuncタイプとActionタイプは「ラムダタイプ」と見なされることがよくありますが、他のデリゲートとまったく同じです。それらの良いところは、基本的に、必要になる可能性のある任意のタイプのデリゲートの名前を定義することです(最大4つのパラメーターですが、独自のパラメーターをさらに追加することもできます)。したがって、多種多様なデリゲートタイプを使用しているが、1回しか使用していない場合は、FuncとActionを使用することで、デリゲート宣言でコードが乱雑になるのを防ぐことができます。

これは、FuncとActionが「ラムダだけではない」ことを示しています。

Int32 DiffOfSquares(Int32 x, Int32 y)
{
  return x*x - y*y;
}

Func<Int32, Int32, Int32> funcPtr = DiffOfSquares;

知っておくと便利なもう1つの点は、同じシグネチャで異なる名前のデリゲート型(メソッド自体ではない)が暗黙的に相互にキャストされないことです。これには、FuncデリゲートとActionデリゲートが含まれます。ただし、署名が同一である場合は、それらの間で明示的にキャストできます。

一歩先を行く.... C#では、関数は柔軟で、ラムダとデリゲートを使用します。ただし、C#には「ファーストクラスの関数」はありません。デリゲート変数に割り当てられた関数の名前を使用して、基本的にその関数を表すオブジェクトを作成できます。しかし、それは本当にコンパイラのトリックです。関数名の後にドットを記述してステートメントを開始すると(つまり、関数自体でメンバーアクセスを実行しようとすると)、参照するメンバーがないことがわかります。オブジェクトからのものでさえありません。これにより、プログラマーは、任意の関数で呼び出すことができる拡張メソッドを追加するなど、便利な(そしてもちろん潜在的に危険な)ことを行うことができなくなります。最善の方法は、Delegateクラス自体を拡張することです。これも確かに便利ですが、それほど多くはありません。

更新:匿名デリゲートとメソッドおよびラムダの違いを示すKargの回答も参照してください。

更新2:James Hartは、非常に技術的ではありますが、ラムダとデリゲートは.NETエンティティではなく(つまり、CLRにはデリゲートまたはラムダの概念がない)、フレームワークと言語の構成要素であることに注意してください。


良い説明。「ファーストクラスのオブジェクト」ではなく、「ファーストクラスの関数」を意味していると思いますが。:)
ibz 2008

1
あなたが正しい。執筆中に文の構造が異なっていて(「C#関数は実際にはファーストクラスのオブジェクトではありません」)、それを変更するのを忘れていました。ありがとう!
Chris Ammerman

通常のメソッドは「ステートメント」で定義されます。ステートメントは、おそらく式に基づいた、命令型プログラムのシーケンス内のアクションです。メソッド定義は別の文法構造ではありませんか?メソッド定義はにリストされていないdocs.microsoft.com/en-us/dotnet/csharp/tour-of-csharp/...
マックスBarraclough

32

質問は少しあいまいです。これは、得られる回答の大きな違いを説明しています。

実際に、.NETFrameworkのラムダとデリゲートの違いは何であるかを尋ねました。それは多くのことの1つかもしれません。あなたは尋ねていますか:

  • C#(またはVB.NET)言語のラムダ式と匿名デリゲートの違いは何ですか?

  • .NET 3.5のSystem.Linq.Expressions.LambdaExpressionオブジェクトとSystem.Delegateオブジェクトの違いは何ですか?

  • またはそれらの両極端の間またはその周辺のどこかに何か?

「C#Lambda式と.NET System.Delegateの違いは何ですか?」という質問に対する答えを提供しようとしているようですが、あまり意味がありません。

.NET Framework自体は、匿名デリゲート、ラムダ式、またはクロージャの概念を理解していません。これらはすべて、言語仕様によって定義されたものです。C#コンパイラが、匿名メソッドの定義を、クロージャ状態を保持するためのメンバー変数を持つ生成されたクラスのメソッドに変換する方法について考えてみてください。.NETにとって、デリゲートについて匿名のものは何もありません。それを書いているC#プログラマーには匿名です。これは、デリゲート型に割り当てられたラムダ式にも同様に当てはまります。

何.NET DOESが理解することは、デリゲートの考えである-メソッドのシグネチャを記述するタイプ、いずれかの特定のオブジェクトの特定のメソッドにバインドされた呼び出しを表すのインスタンス、またはに対して呼び出すことができる特定の種類の特定のメソッドにバインドされていない呼び出し上記のメソッドが上記の署名に準拠している、そのタイプのオブジェクト。このようなタイプはすべてSystem.Delegateから継承します。

.NET 3.5では、System.Linq.Expressions名前空間も導入されています。この名前空間には、コード式を記述するためのクラスが含まれているため、特定の型またはオブジェクトのメソッドへのバインドされた呼び出しまたはバインドされていない呼び出しを表すこともできます。次に、LambdaExpressionインスタンスを実際のデリゲートにコンパイルできます(これにより、式の構造に基づく動的メソッドがコード生成され、それへのデリゲートポインターが返されます)。

C#では、ラムダ式をその型の変数に割り当てることでSystem.Expressions.Expression型のインスタンスを生成できます。これにより、実行時に式を作成するための適切なコードが生成されます。

もちろん、C#のラムダ式と匿名メソッドの違い尋ねていた場合、結局のところ、これはほとんど無関係です。その場合、主な違いは簡潔さです。これは、匿名のデリゲートに傾いています。パラメータを気にし、値を返すことを計画しないでください。型推論されたパラメータと戻り値の型が必要な場合はラムダに向かってください。

また、ラムダ式は式の生成をサポートしています。


3
素晴らしい情報!あなたは私にリフレクターを起動してILを見るように促しました。ラムダが生成されたクラスになるとは知りませんでしたが、考えてみると完全に理にかなっています。
Chris Ammerman

20

1つの違いは、ラムダが正確な署名と一致する必要がある一方で、匿名デリゲートはパラメーターを省略できることです。与えられた:

public delegate string TestDelegate(int i);

public void Test(TestDelegate d)
{}

次の4つの方法で呼び出すことができます(2行目にはパラメーターを持たない匿名のデリゲートがあることに注意してください)。

Test(delegate(int i) { return String.Empty; });
Test(delegate { return String.Empty; });
Test(i => String.Empty);
Test(D);

private string D(int i)
{
    return String.Empty;
}

パラメーターのないラムダ式またはパラメーターのないメソッドを渡すことはできません。これらは許可されていません:

Test(() => String.Empty); //Not allowed, lambda must match signature
Test(D2); //Not allowed, method must match signature

private string D2()
{
    return String.Empty;
}

13

デリゲートは関数ポインター/メソッドポインター/コールバック(選択してください)と同等であり、ラムダはかなり単純化された無名関数です。少なくともそれは私が人々に言うことです。


丁度!違いはありません"。それらは2つの本質的に異なるものです。
ibz 2008

3

これについてはあまり経験がありませんが、デリゲートは任意の関数のラッパーであるのに対し、ラムダ式自体は無名関数であると説明します。


3

デリゲートは常に基本的に関数ポインタです。ラムダはデリゲートに変わることができますが、LINQ式ツリーに変わることもできます。例えば、

Func<int, int> f = x => x + 1;
Expression<Func<int, int>> exprTree = x => x + 1;

最初の行はデリゲートを生成し、2番目の行は式ツリーを生成します。


2
それは本当ですが、それらの違いは、それらが2つの完全に異なる概念であるということです。これは、リンゴとオレンジを比較するようなものです。ダンシールドの答えを参照してください。
ibz 2008

2

ラムダは、デリゲートの単なる構文糖衣です。コンパイラーは最終的にラムダをデリゲートに変換します。

これらは同じだと私は信じています:

Delegate delegate = x => "hi!";
Delegate delegate = delegate(object x) { return "hi";};

2
これらの例はどちらもコンパイルされません。Delegateキーワードである「delegate」からインスタンスの名前を変更しても。
スティーブクーパー

2

デリゲートは関数シグネチャです。何かのようなもの

delegate string MyDelegate(int param1);

デリゲートはボディを実装しません。

ラムダは、デリゲートのシグネチャに一致する関数呼び出しです。上記のデリゲートには、次のいずれかを使用できます。

(int i) => i.ToString();
(int i) => "ignored i";
(int i) => "Step " + i.ToString() + " of 10";

Delegateただし、このタイプの名前は不適切です。型のオブジェクトを作成すると、Delegate実際には、ラムダ、静的メソッド、クラスメソッドなどの関数を保持できる変数が作成されます。


MyDelegate型の変数を作成する場合、それは実際には実行時型ではありません。実行時型はDelegateです。デリゲート、ラムダ、および式ツリーのコンパイル方法に関連するコンパイラのトリックがあります。これにより、コードが正しくないことを暗示していると思います。
Chris Ammerman

2

デリゲートは、特定のパラメーターリストと戻り値の型を持つメソッドへの参照です。オブジェクトが含まれる場合と含まれない場合があります。

ラムダ式は無名関数の形式です。


2

デリゲートは関数ポインタのキューであり、デリゲートを呼び出すと複数のメソッドを呼び出すことができます。ラムダは本質的に匿名のメソッド宣言であり、使用されるコンテキストに応じて、コンパイラーによって異なる方法で解釈される可能性があります。

ラムダ式をメソッドとして指すデリゲートをデリゲートにキャストするか、特定のデリゲートタイプを期待するメソッドにパラメーターとして渡すと、コンパイラーがそれをキャストします。LINQステートメント内で使用すると、ラムダはコンパイラーによって単なるデリゲートではなく式ツリーに変換されます。

違いは、ラムダは別の式の内部でメソッドを定義するための簡潔な方法であるのに対し、デリゲートは実際のオブジェクトタイプであるということです。



1

デリゲートは、実際には関数の構造型付けにすぎません。記名的型付けと、インターフェイスまたは抽象クラスを実装する匿名クラスの実装でも同じことができますが、必要な関数が1つだけの場合は、多くのコードになります。

ラムダは、1930年代のアロンゾチャーチのラムダ計算のアイデアから来ています。これは、関数を作成する匿名の方法です。関数の作成に特に役立ちます

したがって、ラムダはデリゲートのシンタックスシュガーであると言う人もいるかもしれませんが、デリゲートはc#で人々をラムダに簡単にするための架け橋だと思います。


1

ここでいくつかの基本。「デリゲート」は、実際にはメソッドまたはラムダへの参照を保持する変数の名前です。

これは匿名の方法です-

(string testString) => { Console.WriteLine(testString); };

匿名メソッドには名前がないため、これらのメソッドまたは式の両方を割り当てることができるデリゲートが必要です。例の場合

delegate void PrintTestString(string testString); // declare a delegate

PrintTestString print = (string testString) => { Console.WriteLine(testString); }; 
print();

ラムダ式と同じです。通常、それらを使用するにはデリゲートが必要です

s => s.Age > someValue && s.Age < someValue    // will return true/false

funcデリゲートを使用してこの式を使用できます。

Func< Student,bool> checkStudentAge = s => s.Age > someValue && s.Age < someValue ;

bool result = checkStudentAge ( Student Object);

0

ラムダはデリゲートの簡略化されたバージョンです。匿名デリゲートのようなクロージャのプロパティのいくつかがありますが、暗黙の型指定を使用することもできます。このようなラムダ:

something.Sort((x, y) => return x.CompareTo(y));

デリゲートでできることよりもはるかに簡潔です。

something.Sort(sortMethod);
...

private int sortMethod(SomeType one, SomeType two)
{
    one.CompareTo(two)
}

つまり、ラムダは単純化された匿名メソッドのようなものです(デリゲートではありません)。メソッド(匿名かどうか)と同様に、デリゲート変数に割り当てることができます。
ルーカス

0

これは私が私の足の不自由なブログにしばらく置いた例です。ワーカースレッドからラベルを更新したいとします。デリゲート、アノンデリゲート、2種類のラムダを使用してそのラベルを1から50に更新する方法の4つの例があります。

 private void button2_Click(object sender, EventArgs e) 
     { 
         BackgroundWorker worker = new BackgroundWorker(); 
         worker.DoWork += new DoWorkEventHandler(worker_DoWork); 
         worker.RunWorkerAsync(); 
     } 

     private delegate void UpdateProgDelegate(int count); 
     private void UpdateText(int count) 
     { 
         if (this.lblTest.InvokeRequired) 
         { 
             UpdateProgDelegate updateCallBack = new UpdateProgDelegate(UpdateText); 
             this.Invoke(updateCallBack, new object[] { count }); 
         } 
         else 
         { 
             lblTest.Text = count.ToString(); 
         } 
     } 

     void worker_DoWork(object sender, DoWorkEventArgs e) 
     {   
         /* Old Skool delegate usage.  See above for delegate and method definitions */ 
         for (int i = 0; i < 50; i++) 
         { 
             UpdateText(i); 
             Thread.Sleep(50); 
         } 

         // Anonymous Method 
         for (int i = 0; i < 50; i++) 
         { 
             lblTest.Invoke((MethodInvoker)(delegate() 
             { 
                 lblTest.Text = i.ToString(); 
             })); 
             Thread.Sleep(50); 
         } 

         /* Lambda using the new Func delegate. This lets us take in an int and 
          * return a string.  The last parameter is the return type. so 
          * So Func<int, string, double> would take in an int and a string 
          * and return a double.  count is our int parameter.*/ 
         Func<int, string> UpdateProgress = (count) => lblTest.Text = count.ToString(); 
         for (int i = 0; i < 50; i++) 
         { 
             lblTest.Invoke(UpdateProgress, i); 
             Thread.Sleep(50); 
         } 

         /* Finally we have a totally inline Lambda using the Action delegate 
          * Action is more or less the same as Func but it returns void. We could 
          * use it with parameters if we wanted to like this: 
          * Action<string> UpdateProgress = (count) => lblT…*/ 
         for (int i = 0; i < 50; i++) 
         { 
             lblTest.Invoke((Action)(() => lblTest.Text = i.ToString())); 
             Thread.Sleep(50); 
         } 
     }

0

.NETは単独では、つまりc#なしではデリゲートとラムダ式の理解が得られないため、質問のあいまいさのために、質問は.NETではなくc#に関係していると思います。

通常、いわゆるジェネリックデリゲートとは反対に、後で参照)デリゲートtypedefは、関数ポインタ型の一種のc ++ 、たとえばc ++と見なす必要があります。

R (*thefunctionpointer) ( T ) ;

typedefthefunctionpointerは、型のオブジェクトを受け取り、型のオブジェクトをT返す関数へのポインタの型である型Rです。あなたはこのようにそれを使うでしょう:

thefunctionpointer = &thefunction ;
R r = (*thefunctionpointer) ( t ) ; // where t is of type T

thefunctionを取り、Tを返す関数はどこにありますかR

C#ではあなたは

delegate R thedelegate( T t ) ; // and yes, here the identifier t is needed

そして、あなたはそれをこのように使うでしょう:

thedelegate thedel = thefunction ;
R r = thedel ( t ) ; // where t is of type T

thefunctionを取り、Tを返す関数はどこにありますかR。これはデリゲート、いわゆる通常のデリゲート用です。

これで、c#にもジェネリックデリゲートがあります。これはジェネリックなデリゲートです。つまり、c ++式を使用して、いわば「テンプレート化」されています。それらは次のように定義されます:

public delegate TResult Func<in T, out TResult>(T arg);

そして、あなたはこれらをこのように使うことができます:

Func<double, double> thefunctor = thefunction2; // call it a functor because it is
                                                // really as a functor that you should
                                                // "see" it
double y = thefunctor(2.0);

ここで、thefunction2は引数を取り、を返す関数です。doubleです。

ここで、今のthefunction2ところどこにも定義されていない「関数」をステートメントで使用したいのではなく、後で使用することはないと想像してみてください。次に、c#を使用すると、この関数のを使用できます。式とは、その「数学的な」(またはプログラムに固執するための機能的な)式を意味します。たとえば、:double xにを関連付けますdouble x*x。数学では、「\ mapsto」ラテックス記号を使用しこれを記述します。C#では、関数表記が借用されています:=>。例えば ​​:

Func<double, double> thefunctor = ( (double x) => x * x ); // outer brackets are not
                                                           // mandatory

(double x) => x * xある式が。タイプではありませんが、デリゲート(ジェネリックかどうか)はタイプです。

道徳?最後に、関数ポインター型(またはラップされた+スマート+ジェネリック関数ポインター型)でない場合、デリゲート(またはジェネリックデリゲート)とは何ですか?他の何か!これあれを参照してください。


-1

まあ、本当に過度に単純化されたバージョンは、ラムダが無名関数の単なる省略形であるということです。デリゲートは、イベント、非同期呼び出し、複数のメソッドチェーンなど、匿名関数以外にも多くのことを実行できます。


1
ラムダはイベントハンドラーとして使用できます。button.Click + =(sender、eventArgs)=> {MessageBox.Show( "Click"); }そして非同期的に呼び出されますnewSystem.Threading.Thread(()=> Console.Write( "Executed on a thread"))。Start();
スティーブクーパー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.