この質問はよく聞かれるので、違いを最もよく説明する方法について意見を求めたいと思いました。
18 に答える
それらは実際には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 つのパラメーターですが、独自のパラメーターをさらに追加することもできます)。そのため、さまざまなデリゲート型を使用しているが、一度しか使用していない場合は、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 にはデリゲートまたはラムダの概念がありません)、フレームワークと言語の構成要素であるという、非常に技術的ではありますが、重要な注意を払っています。
質問は少しあいまいです。これは、得られる回答の大きな違いを説明しています。
.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が理解するのは、デリゲートの概念です。デリゲートの概念は、メソッドシグネチャを記述します。そのインスタンスは、特定のオブジェクトの特定のメソッドへのバインドされた呼び出し、または特定のタイプの特定のメソッドへのバインドされていない呼び出しのいずれかを表します。上記のメソッドが上記の署名に準拠している、そのタイプのオブジェクト。このようなタイプはすべてSystem.Delegateから継承します。
.NET 3.5では、System.Linq.Expressions名前空間も導入されています。この名前空間には、コード式を記述するためのクラスが含まれているため、特定の型またはオブジェクトのメソッドへのバインドされた呼び出しまたはバインドされていない呼び出しを表すこともできます。次に、LambdaExpressionインスタンスを実際のデリゲートにコンパイルできます(これにより、式の構造に基づく動的メソッドがコード生成され、それへのデリゲートポインターが返されます)。
C#では、ラムダ式をその型の変数に割り当てることでSystem.Expressions.Expression型のインスタンスを生成できます。これにより、実行時に式を構築するための適切なコードが生成されます。
もちろん、ラムダ式とC#の匿名メソッドの違いを尋ねていた場合、結局のところ、これはほとんど無関係です。その場合、主な違いは簡潔さです。パラメータを気にし、値を返すことを計画しないでください。型推論されたパラメータと戻り型が必要な場合はラムダに向かってください。
また、ラムダ式は式の生成をサポートしています。
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;
}
デリゲートは、関数ポインター/メソッド ポインター/コールバック (選択してください) と同等であり、ラムダは非常に単純化された無名関数です。少なくとも私は人々にそう言っています。
デリゲートは常に、基本的には単なる関数ポインターです。ラムダはデリゲートに変換できますが、LINQ 式ツリーに変換することもできます。例えば、
Func<int, int> f = x => x + 1;
Expression<Func<int, int>> exprTree = x => x + 1;
最初の行はデリゲートを生成し、2 番目の行は式ツリーを生成します。
私はこれについて多くの経験を持っていませんが、それを説明する方法は、デリゲートは任意の関数のラッパーであるのに対し、ラムダ式はそれ自体が無名関数であるということです。
デリゲートは、特定のパラメーター リストと戻り値の型を持つメソッドへの参照です。オブジェクトを含む場合と含まない場合があります。
ラムダ式は無名関数の形式です。
ラムダは、デリゲートの単なる構文糖衣です。コンパイラは最終的にラムダをデリゲートに変換します。
これらは同じです、私は信じています:
Delegate delegate = x => "hi!";
Delegate delegate = delegate(object x) { return "hi";};
「ラムダと匿名デリゲートの違いは何ですか?」という質問が意図されていたことは明らかです。ここでのすべての回答のうち、1 人だけが正しく答えました。主な違いは、ラムダを使用して式ツリーとデリゲートを作成できることです。
MSDN で詳細を読むことができます: http://msdn.microsoft.com/en-us/library/bb397687.aspx
デリゲートは関数シグネチャです。何かのようなもの
delegate string MyDelegate(int param1);
デリゲートは本体を実装しません。
ラムダは、デリゲートの署名に一致する関数呼び出しです。上記のデリゲートには、次のいずれかを使用できます。
(int i) => i.ToString();
(int i) => "ignored i";
(int i) => "Step " + i.ToString() + " of 10";
ただし、このDelegate
型の名前は不適切です。タイプのオブジェクトを作成すると、Delegate
実際には関数を保持できる変数が作成されます-ラムダ、静的メソッド、またはクラスメソッドです。
デリゲートは関数ポインターのキューであり、デリゲートを呼び出すと複数のメソッドが呼び出される場合があります。ラムダは本質的に匿名のメソッド宣言であり、使用されるコンテキストに応じて、コンパイラによって異なる方法で解釈される可能性があります。
ラムダ式をデリゲートにキャストすることにより、メソッドとしてラムダ式を指すデリゲートを取得できます。または、特定のデリゲート型を期待するメソッドにパラメーターとして渡す場合、コンパイラはそれをキャストします。これを LINQ ステートメント内で使用すると、ラムダはコンパイラによって単なるデリゲートではなく式ツリーに変換されます。
実際の違いは、ラムダは別の式の中でメソッドを定義する簡単な方法であるのに対し、デリゲートは実際のオブジェクト型であるということです。
デリゲートは、関数の構造的な型付けにすぎません。名目上の型付けと、インターフェイスまたは抽象クラスを実装する匿名クラスの実装で同じことを行うことができますが、必要な関数が 1 つだけの場合、多くのコードが必要になります。
ラムダは、1930 年代のアロンゾ教会のラムダ計算のアイデアから来ています。関数を作成する匿名の方法です。それらは関数を構成するのに特に役立ちます
したがって、ラムダはデリゲートのシンタックス シュガーであると言う人もいるかもしれませんが、デリゲートは、C# で人々をラムダに簡単に移行するための架け橋であると言えます。
ラムダは、デリゲートの簡略化されたバージョンです。これらは、匿名デリゲートのようなクロージャーのプロパティの一部を備えていますが、暗黙の型指定を使用することもできます。このようなラムダ:
something.Sort((x, y) => return x.CompareTo(y));
デリゲートでできることよりもはるかに簡潔です。
something.Sort(sortMethod);
...
private int sortMethod(SomeType one, SomeType two)
{
one.CompareTo(two)
}
これは、私の不自由なブログにしばらく掲載した例です。ワーカー スレッドからラベルを更新したいとします。デリゲート、アノン デリゲート、および 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);
}
}
まあ、本当に単純化しすぎたバージョンは、ラムダが無名関数の省略形に過ぎないということです。デリゲートは、匿名関数だけでなく、イベント、非同期呼び出し、複数のメソッド チェーンなど、多くのことを実行できます。