わからないことを質問するのは素晴らしいことですが、問題は、相手がどの部分を理解していないのかを知るのが難しいことです。あなたが知っていることをたくさん教えて、実際にあなたの質問に答えないのではなく、ここで役に立てば幸いです.
Linq の前、式の前、ラムダの前、匿名デリゲートの前の時代に戻りましょう。
.NET 1.0 では、これらはありませんでした。ジェネリックすらありませんでした。代表者がいましたが。また、デリゲートは、関数ポインター (C、C++、またはそのような言語を知っている場合) または引数/変数としての関数 (Javascript またはそのような言語を知っている場合) に関連しています。
デリゲートを定義できます。
public delegate int MyDelegate(double someValue, double someOtherValue);
そして、それをフィールド、プロパティ、変数、メソッド引数の型として、またはイベントの基礎として使用します。
しかし、当時、デリゲートに実際に値を与える唯一の方法は、実際のメソッドを参照することでした。
public int CompareDoubles(double x, double y)
{
if (x < y) return -1;
return y < x ? 1 : 0;
}
MyDelegate dele = CompareDoubles;
dele.Invoke(1.0, 2.0)
または省略形でそれを呼び出すことができdele(1.0, 2.0)
ます。
現在、.NET にはオーバーロードがあるため、参照するものを複数持つことができますCompareDoubles
。これは問題ではありません。たとえば、コンパイラは、もう一方をpublic int CompareDoubles(double x, double y, double z){…}
割り当てることを意図していた可能性があることを認識できるため、あいまいではないからです。それでも、コンテキスト内では 2 つの引数を取り、 を返すメソッドを意味しますが、そのコンテキスト外では、その名前を持つすべてのメソッドのグループを意味します。CompareDoubles
dele
CompareDoubles
double
int
CompareDoubles
したがって、私たちがそれを呼んでいるのはメソッドグループです。
現在、.NET 2.0 ではデリゲートに役立つジェネリックがあり、同時に C#2 では匿名メソッドもあり、これも便利です。2.0 の時点で、次のことができるようになりました。
MyDelegate dele = delegate (double x, double y)
{
if (x < y) return -1;
return y < x ? 1 : 0;
};
この部分は C#2 のシンタックス シュガーにすぎず、舞台裏にはまだメソッドが存在しますが、それには「言いようのない名前」 (.NET 名としては有効だが C# 名としては無効な名前) が含まれているため、C#名前が衝突することはありません)。ただし、よくあることですが、特定のデリゲートで一度だけ使用するためだけにメソッドを作成する場合は便利でした。
もう少し先に進むと、.NET 3.5 では、共変性と反変性 (デリゲートに最適)Func
とAction
デリゲート (多くの場合非常に似ているさまざまなデリゲートの束を持つのではなく、型に基づいて同じ名前を再利用するのに最適) とそれに沿ったものがあります。それに伴い、ラムダ式を持つ C#3 が登場しました。
これらは、ある用途では匿名メソッドに少し似ていますが、別の用途ではそうではありません。
そのため、次のことができません。
var func = (int i) => i * 2;
var
割り当てられたものからそれが何を意味するかを理解しますが、ラムダは割り当てられたものからそれらが何であるかを理解するので、これはあいまいです。
次のことを意味します。
Func<int, int> func = i => i * 2;
その場合、次の省略形です。
Func<int, int> func = delegate(int i){return i * 2;};
これは、次のような省略形です。
int <>SomeNameImpossibleInC# (int i)
{
return i * 2;
}
Func<int, int> func = <>SomeNameImpossibleInC#;
ただし、次のようにも使用できます。
Expression<Func<int, int>> func = i => i * 2;
これは次の省略形です。
Expression<Func<int, int>> func = Expression.Lambda<Func<int, int>>(
Expression.Multiply(
param,
Expression.Constant(2)
),
param
);
また、.NET 3.5 には、これらの両方を多用する Linq があります。実際、Expressions は Linq の一部と見なされ、System.Linq.Expressions
名前空間にあります。ここで取得するオブジェクトは、実行方法ではなく、実行したいこと (パラメーターを取得して 2 倍し、結果を返す) の説明であることに注意してください。
現在、Linq は主に 2 つの方法で動作します。そしてIQueryable
、そして。IQueryable<T>
_ 前者は「プロバイダー」で使用される操作を定義し、「プロバイダーが行うこと」はそのプロバイダー次第であり、後者はメモリ内の値のシーケンスで同じ操作を定義します。IEnumerable
IEnumerable<T>
ある場所から別の場所に移動できます。を を にIEnumerable<T>
変換するIQueryable<T>
とAsQueryable
、その列挙可能なラッパーが提供さIQueryable<T>
れIEnumerable<T>
ます。IQueryable<T>
IEnumerable<T>
列挙可能なフォームはデリゲートを使用します。仕組みの簡略化されたバージョンSelect
(このバージョンでは省略されている最適化が多数あり、エラー チェックをスキップし、エラー チェックがすぐに行われるようにするために間接的にしています) は次のようになります。
public static IEnumerable<TResult> Select(this IEnumerable<TSource> source, Func<TSource, TResult> selector)
{
foreach(TSource item in source) yield return selector(item);
}
一方、クエリ可能なバージョンはExpression<TSource, TResult>
、 への呼び出しを含む式の一部にすることで式ツリーを取得しSelect
、クエリ可能なソースを取得して、その式をラップするオブジェクトを返します。つまり、queryable の呼び出しは、queryable の!Select
への呼び出しを表すオブジェクトを返します。Select
それをどうするかは、プロバイダーによって異なります。データベース プロバイダーはそれらを SQL に変換し、列挙Compile()
型は式を呼び出してデリゲートを作成し、上記の最初のバージョンに戻りますSelect
。
しかし、その歴史を考慮して、もう一度歴史をさかのぼってみましょう。ラムダは、式またはデリゲートのいずれかを表すことができます (式の場合はCompile()
、同じデリゲートを取得できます)。デリゲートは、変数を介してメソッドを指す方法であり、メソッドはメソッド グループの一部です。これらはすべて、最初のバージョンではメソッドを作成してそれを渡すことによってのみ呼び出すことができたテクノロジーに基づいて構築されています。
ここで、単一の引数を取り、結果を持つメソッドがあるとしましょう。
public string IntString(int num) { return num.ToString(); }
次に、ラムダ セレクターで参照したとします。
Enumerable.Range(0, 10).Select(i => IntString(i));
デリゲートの匿名メソッドを作成するラムダがあり、その匿名メソッドは同じ引数と戻り値の型を持つメソッドを呼び出します。ある意味では、次のようになります。
public string MyAnonymousMethod(int i){return IntString(i);}
MyAnonymousMethod
ここでは少し無意味です。呼び出しIntString(i)
て結果を返すだけなのでIntString
、最初に呼び出してそのメソッドを切り取ってはいけません。
Enumerable.Range(0, 10).Select(IntString);
ラムダベースのデリゲートを取得してメソッド グループに変換することで、不必要な (ただし、デリゲートのキャッシュについては以下の注を参照してください) レベルの間接化を削除しました。したがって、ReSharper のアドバイスは「メソッド グループに変換する」ですが、それは言葉遣いです (私自身は ReSharper を使用していません)。
ただし、ここで注意することがあります。IQueryable<T>
の Select は式のみを受け取るため、プロバイダーはそれを処理方法 (データベースに対する SQL など) に変換する方法を考え出すことができます。IEnumerable<T>
の Select はデリゲートのみを受け取るため、デリゲートは .NET アプリケーション自体で実行できます。で前者から後者に移動できます (クエリ可能なものが実際にラップされた列挙可能である場合) がCompile()
、後者から前者に移動することはできません: デリゲートを取得してそれをに変換する方法がありません。 SQL に変換できるものではない「このデリゲートを呼び出す」以外を意味する式。
ラムダ式を使用すると、一緒に使用するとi => i * 2
式になり、一緒に使用するIQueryable<T>
とデリゲートになりIEnumerable<T>
ます。クエリ可能な式を優先するオーバーロード解決ルールのためです (型として両方を処理できますが、式の形式は最も派生したもので機能します)。タイプ)。明示的にデリゲートを指定したとしても、それをどこかに入力したためかFunc<>
、メソッド グループから取得したかどうかにかかわらず、式を受け取るオーバーロードは使用できず、デリゲートを受け取るオーバーロードが使用されます。つまり、データベースに渡されるのではなく、その時点までの linq 式が「データベース部分」になり、それが呼び出され、残りの作業がメモリ内で行われます。
回避するのが最善の時間の 95% です。したがって、95% の確率で、データベースに基づくクエリで「メソッド グループに変換する」というアドバイスを受け取った場合、「うーん、これは実際にはデリゲートだ。なぜデリゲートなのか? 式に変更できますか?」と考える必要があります。 "。残りの 5% の時間だけ、「メソッド名を渡すだけで少し短くなる」と考えてください。(また、デリゲートの代わりにメソッド グループを使用すると、コンパイラが別の方法で実行できるデリゲートのキャッシュが妨げられるため、効率が低下する可能性があります)。
そこで、あなたがその過程で理解できなかった部分をカバーしたことを願っています。または、少なくともここには、指して「そこの部分、それは私が理解していない部分です」と言うことができる部分があることを願っています.