Anders Hejlsberg, Mads Torgersen
March 2007
日本語版最終更新日 2007 年 10 月 4 日
適用対象 :
Visual C# 3.0
概要 : C# 3.0 の技術概要です。高次で関数スタイルのクラス ライブラリの作成と使用をサポートする、C# 2.0 を基盤としたいくつかの言語拡張を紹介します。
目次
はじめに
2 6.1 暗黙的に型指定されたローカル変数
2 6.2 拡張メソッド
2 6.2.1 拡張メソッドの宣言
2 6.2.2 使用できる拡張メソッド
2 6.2.3 拡張メソッドの呼び出し
2 6.3 ラムダ式
26.3.1 匿名メソッドとラムダ式の変換
2 6.3.2 デリゲート作成式
2 6.3.3 型推論
2 6.3.3.1 第 1 段階
2 6.3.3.2 第 2 段階
2 6.3.3.3 入力の型
2 6.3.3.4 出力の型
2 6.3.3.5 依存
2 6.3.3.6 出力の型推論
2 6.3.3.7 明示的な引数の型推論
2 6.3.3.8 厳密な推論
2 6.3.3.9 下限の推論
2 6.3.3.10 固定
2 6.3.3.11 推論される戻り値の型
2 6.3.3.12 メソッド グループの変換での型推論
2 6.3.3.13 式のセットについての最適な共通の型を突き止める
2 6.3.4 オーバーロードの解決
2 6.4 オブジェクト初期化子とコレクション初期化子
2 6.4.1 オブジェクト初期化子
2 6.4.2 コレクション初期化子
2 6.5 匿名型
2 6.6 暗黙的に型指定された配列
2 6.7 クエリ式
2 6.7.1 クエリ式の変換
2 6.7.1.1 続きのある select 句と groupby 句
2 6.7.1.2 範囲変数の明示的な型
2 6.7.1.3 逆クエリ式
2 6.7.1.4 from 句、let 句、where 句、join 句、orderby 句
2 6.7.1.5 select 句
2 6.7.1.6 groupby 句
2 6.7.1.7 透過識別子
2 6.7.2 クエリ式パターン
2 6.8 式のツリー
2 6.8.1 オーバーロードの解決
2 6.9 自動的に実装されるプロパティ
はじめに
この資料には、Visual C# 3.0 に適用されるいくつかの更新内容が含まれています。この言語のリリース時には、総合的な仕様書が提供される予定です。
C# 3.0 では、高次で関数スタイルのクラス ライブラリの作成と使用をサポートする、C# 2.0 を基盤としたいくつかの言語拡張が導入されています。こうした言語拡張により、リレーショナル データベースや XML などの分野でのクエリ言語と同等の表現力を持つ合成 API の構築が可能になります。拡張機能には以下のようなものがあります。
- 暗黙的に型指定されたローカル変数。これを使用すると、ローカル変数の初期化に使用される式から、ローカル変数の型を推論することができます。
- 拡張メソッド。これを使用すると、既存の型や構築された型を追加のメソッドにより拡張することができます。
- ラムダ式。これは匿名メソッドの進化形で、より高度な型推論、およびデリゲート型や式のツリーへの変換を提供します。
- オブジェクト初期化子。これにより、オブジェクトの構築と初期化が簡単になります。
- 匿名型。これは、オブジェクト初期化子から自動的に推論され作成されるタプル型です。
- 暗黙的に型指定された配列。これは、配列初期化子から配列の要素の型を推論する、配列の作成と初期化の形式です。
- クエリ式。これは、SQL や XQuery などのリレーショナル クエリ言語および階層クエリ言語に似た、言語に統合されたクエリ用の構文を提供します。
- 式のツリー。これを使用すると、ラムダ式をコード (デリゲート型) としてではなくデータ (式のツリー) として表現することができます。
このドキュメントでは、これらの機能の技術概要を提供します。このドキュメントでは、『C# 言語仕様 Version 1.2』(§1 ~ §18) と『C# バージョン 2.0 の仕様』(§19 ~ §25) に言及しています。これらの仕様は、Visual Studio 2005 インストール ディレクトリ以下の "VC#\Specifications\1041\" ディレクトリから入手できます。
26.1 暗黙的に型指定されたローカル変数
暗黙的に型指定されたローカル変数宣言では、宣言されるローカル変数の型は、その変数の初期化に使用される式から推論されます。ローカル変数宣言で型として var を指定しており、var という名前の型がスコープ内にない場合、その宣言は、暗黙的に型指定されたローカル変数宣言です。次に例を示します。
var i = 5;
var s = "Hello";
var d = 1.0;
var numbers = new int[] {1, 2, 3};
var orders = new Dictionary<int,Order>();
上記の暗黙的に型指定されたローカル変数宣言は、以下に示す、明示的に型指定された宣言とまったく同等です。
int i = 5;
string s = "Hello";
double d = 1.0;
int[] numbers = new int[] {1, 2, 3};
Dictionary<int,Order> orders = new Dictionary<int,Order>();
暗黙的に型指定されたローカル変数宣言のローカル変数宣言子には、以下の制限があります。
- 宣言子に初期化子が含まれている必要があります。
- 初期化子は式である必要があります。
- 初期化子式にはコンパイル時の型を指定する必要があり、この型は null 型以外の型である必要があります。
- ローカル変数宣言に複数の宣言子を含めることはできません。
- 宣言した変数自体を初期化子で参照することはできません。
暗黙的に型指定されたローカル変数宣言の間違った例を以下に示します。
var x; // エラー。型推論に使用する初期化子がない。
var y = {1, 2, 3}; // エラー。コレクション初期化子は許可されていない。
var z = null; // エラー。null 型は許可されていない。
var u = x => x + 1; // エラー。ラムダ式で型が指定されていない。
var v = v++; // エラー。変数自体を初期化子で参照することはできない。
ローカル変数宣言で型として var を指定し、var という名前の型がスコープ内にある場合、下位互換性の理由から、その宣言はその型を参照します。ただし、var という名前の型は、"型名は大文字で開始する" という確立した規約に違反するので、このような状況が発生することはめったにないでしょう。
for ステートメント (§8.8.3) の for-initializer および using ステートメント (§8.13) の resource-acquisition は、暗黙的に型指定されたローカル変数宣言にすることができます。同様に、foreach ステートメント (§8.8.4) の繰り返し変数は、暗黙的に型指定されたローカル変数として宣言することができます。この場合、繰り返し変数の型は、列挙されているコレクションの要素の型であると推論されます。次に例を示します。
int[] numbers = { 1, 3, 5, 7, 9 };
foreach (var n in numbers) Console.WriteLine(n);
この例では、n の型は、numbers の要素の型である int だと推論されます。
暗黙的に型指定されたローカル変数宣言を含むことができるのは、local-variable-declaration、for-initializer、resource-acquisition、および foreach-statement のみです。
26.2 拡張メソッド
拡張メソッドは、インスタンス メソッド構文を使用して呼び出すことができる静的メソッドです。実質的には、拡張メソッドを使用すると、既存の型や構築された型を追加のメソッドにより拡張することができます。
注 拡張メソッドは、インスタンス メソッドに比べて、見つけにくく、機能も限られています。このため、拡張メソッドの使用はできるだけ控え、インスタンス メソッドが適していない場合や使用できない場合にのみ使用することをお勧めします。プロパティ、イベント、演算子など、他の種類の拡張メンバについては検討中ですが、現在のところサポートされていません。
26.2.1 拡張メソッドの宣言
拡張メソッドは、メソッドの 1 つ目のパラメータに修飾子としてキーワード this を指定することにより宣言します。拡張メソッドは、非ジェネリックの、入れ子になっていない静的クラスでのみ宣言可能です。2 つの拡張メソッドを宣言する静的クラスの例を以下に示します。
namespace Acme.Utilities
{
public static class Extensions
{
public static int ToInt32(this string s) {
return Int32.Parse(s);
}
public static T[] Slice<T>(this T[] source, int index, int count) {
if (index < 0 || count < 0 || source.Length ? index < count)
throw new ArgumentException();
T[] result = new T[count];
Array.Copy(source, index, result, 0, count);
return result;
}
}
}
拡張メソッドの 1 つ目のパラメータには this 以外の修飾子を指定することはできません。また、このパラメータの型をポインタ型にすることはできません。
拡張メソッドには、通常の静的メソッドの機能がすべて備わっています。また、拡張メソッドは、インポートすると、インスタンス メソッド構文を使用して呼び出すことができます。
26.2.2 使用できる拡張メソッド
拡張メソッドは、静的クラス内で宣言するか、名前空間内で using-namespace-directive (§9.3.2) を使用してインポートすると、名前空間内で使用することができます。したがって、using-namespace-directive を使用すると、インポートした名前空間に含まれる型がインポートされるだけでなく、インポートした名前空間内のすべての静的クラスのすべての拡張メソッドがインポートされます。
実質的には、使用できる拡張メソッドは、1 つ目のパラメータで指定した型の追加のメソッドとして扱われ、通常のインスタンス メソッドよりも優先度は低くなります。たとえば、以下のように using-namespace-directive を使用して上記の例の Acme.Utilities 名前空間をインポートしたとします。
この場合、以下のようにインスタンス メソッド構文を使用して静的クラス Extensions 内の拡張メソッドを呼び出すことが可能になります。
string s = "1234";
int i = s.ToInt32(); // Extensions.ToInt32(s) と同じ
int[] digits = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
int[] a = digits.Slice(4, 3); // Extensions.Slice(digits, 4, 3) と同じ
26.2.3 拡張メソッドの呼び出し
ここでは、拡張メソッドの呼び出しに関する詳細な規則について説明します。以下に示す形式のメソッド呼び出し (§7.5.5.1)
expr . identifier ( )
expr . identifier ( args )
expr . identifier < typeargs > ( )
expr . identifier < typeargs > ( args )
のそれぞれでは、通常の呼び出し処理によって適切なインスタンス メソッドが検出されなかった場合 (具体的には、呼び出しの候補となるメソッドのセットが空の場合)、構文を拡張メソッドの呼び出しとして処理するよう試みられます。まず、メソッド呼び出しはそれぞれ以下のいずれかに書き換えられます。
identifier ( expr )
identifier ( expr , args )
identifier < typeargs > ( expr )
identifier < typeargs > ( expr , args )
続いて、書き換えられた形式は、identifier の解決方法以外は静的メソッドの呼び出しとして処理されます。identifier で指定された名前を持つ名前空間内の使用可能でアクセス可能なすべての拡張メソッドで構成されるメソッド グループについて、書き換えられたメソッド呼び出しの一連の処理が試みられます。これは、外側を囲む最も近い名前空間宣言から始まり、外側を囲む各名前空間宣言で継続され、それを含むコンパイル ユニットで終わります。このセットから、適切でないメソッド (§7.4.2.1)、および、1 つ目の引数から 1 つ目のパラメータへの暗黙的な、ID 変換、参照の変換、またはボックス化変換が存在しないメソッドをすべて除外します。このような候補メソッドの、空でないセットをもたらす最初のメソッド グループが、書き換えられたメソッド呼び出し用に選択されるグループであり、最適な拡張メソッドを候補のセットから選択するために通常のオーバーロードの解決 (§7.4.2) が適用されます。すべての試みが完了しても候補メソッドのセットが空の場合は、コンパイル時のエラーが発生します。
上記の規則は、インスタンス メソッドの方が拡張メソッドよりも優先され、内側の名前空間宣言で使用できる拡張メソッドの方が外側の名前空間宣言で使用できる拡張メソッドよりも優先されることを意味します。例を以下に示します。
public static class E
{
public static void F(this object obj, int i) { }
public static void F(this object obj, string s) { }
}
class A { }
class B
{
public void F(int i) { }
}
class C
{
public void F(object obj) { }
}
class X
{
static void Test(A a, B b, C c) {
a.F(1); // E.F(object, int)
a.F("hello"); // E.F(object, string)
b.F(1); // B.F(int)
b.F("hello"); // E.F(object, string)
c.F(1); // C.F(object)
c.F("hello"); // C.F(object)
}
}
この例では、B のメソッドは 1 つ目の拡張メソッドよりも優先され、C のメソッドはどちらの拡張メソッドよりも優先されます。
26.3 ラムダ式
C# 2.0 で導入された匿名メソッドを使用すると、デリゲート値が見込まれる箇所でコード ブロックを "インラインに" 記述することができます。匿名メソッドを使用すると、関数型プログラミング言語の表現力の多くが提供されますが、匿名メソッド構文には、その構文がやや冗長で義務的であるという性質があります。ラムダ式を使用すると、より簡潔で機能的な構文で匿名メソッドを記述することができます。
ラムダ式は、パラメータ リスト、それに続く => トークン、および、それに続く式またはステートメント ブロックとして記述します。
expression:
assignment
non-assignment-expression
non-assignment-expression:
conditional-expression
lambda-expression
query-expression
lambda-expression:
( lambda-parameter-listopt
)
=> lambda-expression-body
implicitly-typed-lambda-parameter => lambda-expression-body
lambda-parameter-list:
explicitly-typed-lambda-parameter-list
implicitly-typed-lambda-parameter-list
explicitly-typed-lambda-parameter-list
explicitly-typed-lambda-parameter
explicitly-typed-lambda-parameter-list , explicitly-typed-lambda-parameter
explicitly-typed-lambda-parameter:
parameter-modifieropt type identifier
implicitly-typed-lambda-parameter-list
implicitly-typed-lambda-parameter
implicitly-typed-lambda-parameter-list , implicitly-typed-lambda-parameter
implicitly-typed-lambda-parameter:
identifier
lambda-expression-body:
expression
block
=> 演算子の優先順位は代入 (=) と同じです。また、この演算子は右結合です。
ラムダ式のパラメータは、明示的に型指定することも暗黙的に型指定することもできます。明示的に型指定されたパラメータ リストでは、各パラメータの型は明示的に指定されます。暗黙的に型指定されたパラメータ リストでは、パラメータの型は、ラムダ式が使用されるコンテキストから推論されます。具体的には、ラムダ式が互換性のあるデリゲート型に変換されるときに、そのデリゲート型によってパラメータの型が提供されます (§26.3.1)。
パラメータが、暗黙的に型指定されたもの 1 つのみであるラムダ式では、パラメータ リストのかっこを省略することができます。つまり、
という形式のラムダ式は、以下のように略記することができます。
ラムダ式の例を以下にいくつか示します。
x => x + 1 // 暗黙的な型指定。本体が式。
x => { return x + 1; } // 暗黙的な型指定。本体がステートメント。
(int x) => x + 1 // 明示的な型指定。本体が式。
(int x) => { return x + 1; } // 明示的な型指定。本体がステートメント。
(x, y) => x * y // 複数のパラメータ。
() => Console.WriteLine() // パラメータなし。
『C# バージョン 2.0 の仕様』の §21 に記載されている匿名メソッドの仕様は、ラムダ式にもおおむね当てはまります。ラムダ式は、以下の点を除いて、機能的には匿名メソッドに似ています。
- 匿名メソッドでは、パラメータ リストを完全に省略して、あらゆるパラメータ リストのデリゲート型に変換できるようにすることができます。
- ラムダ式では、パラメータの型を省略し推論させることができますが、匿名メソッドでは、パラメータの型を明示的に指定する必要があります。
- ラムダ式の本体には、式もステートメント ブロックも指定できますが、匿名メソッドの本体に指定できるのはステートメント ブロックのみです。
- 本体が式のラムダ式は、式のツリーに変換することができます (§26.8)。
26.3.1 匿名メソッドとラムダ式の変換
注 このセクションは §21.3 に代わるものです。
anonymous-method-expression と lambda-expression は、特別な変換規則を持つ値として分類されます。このような値には型がありませんが、互換性のあるデリゲート型に暗黙的に変換可能です。具体的には、以下の条件に該当する場合に、デリゲート型 D は匿名メソッドまたはラムダ式の L と互換性があります。
- D と L のパラメータ数が同じです。
- L が anonymous-method-signature を含まない匿名メソッドである場合、D のパラメータが、out パラメータ修飾子が指定されたパラメータ以外であれば、D は 0 個以上のどのような型のパラメータでも持つことができます。
- L が明示的に型指定されたパラメータ リストを持つ場合、D の各パラメータの型および修飾子は、L の対応するパラメータと同じです。
- L が暗黙的に型指定されたパラメータ リストを持つラムダ式の場合、D は ref パラメータまたは out パラメータを持ちません。
- D の戻り値の型が void であり、L の本体が式の場合、L の各パラメータに D の対応するパラメータの型が指定してあれば、L の本体は、statement-expression (§8.6) として許可されている有効な式 (§7 参照) です。
- D の戻り値の型が void であり、L の本体がステートメント ブロックの場合、L の各パラメータに D の対応するパラメータの型が指定してあれば、L の本体は、return ステートメントが式を指定していない有効なステートメント ブロック (§8.2 参照) です。
- D の戻り値の型が void 以外であり、L の本体が式の場合、L の各パラメータに D の対応するパラメータの型が指定してあれば、L の本体は、D の戻り値の型に暗黙的に変換可能である有効な式 (§7 参照) です。
- D の戻り値の型が void 以外であり、L の本体がステートメント ブロックの場合、L の各パラメータに D の対応するパラメータの型が指定してあれば、L の本体は、各 return ステートメントが D の戻り値の型に暗黙的に変換可能な式を指定する、到達不能なエンド ポイントを持つ、有効なステートメント ブロック (§8.2 参照) です。
次の例では、型 A の引数を受け取り、型 R の値を返す関数を表す、ジェネリック デリゲート型 Func<A,R> を使用しています。
delegate R Func<A,R>(A arg);
次の代入式
Func<int,int> f1 = x => x + 1; // OK
Func<int,double> f2 = x => x + 1; // OK
Func<double,int> f3 = x => x + 1; // エラー
では、各ラムダ式の、パラメータと戻り値の型は、ラムダ式の代入先の変数の型から決定されます。1 つ目の代入式では、ラムダ式がデリゲート型 Func<int,int> に正常に変換されます。x に型 int が指定されている場合、x + 1 は型 int に暗黙的に変換可能な有効な式であるためです。同様に、2 つ目の代入式では、ラムダ式がデリゲート型 Func<int,double> に正常に変換されます。これは x + 1 の結果 (型 int) は型 double に暗黙的に変換可能であるためです。しかし、3 つ目の代入式では、コンパイル時のエラーになります。これは x に型 double が指定されている場合、x + 1 の結果 (型 double) は型 int に暗黙的に変換できないためです。
26.3.2 デリゲート作成式
注 このセクションは §21.10 に代わるものです。
デリゲート作成式 (§7.5.10.3) は拡張され、引数には、メソッド グループとして分類される式、匿名メソッドやラムダ式として分類される式、またはデリゲート型の値を指定できるようになりました。
new D(E) という形式 (D は delegate-type、E は expression) の delegate-creation-expression のコンパイル時の処理は、次の手順で行われます。
- E がメソッド グループの場合は、E から D へのメソッド グループの変換 (§21.9) が存在する必要があり、デリゲート作成式はその変換と同じ方法で処理されます。
- E が匿名メソッドまたはラムダ式の場合は、E から D への、匿名メソッドまたはラムダ式の変換 (§ 26.3.1) が存在する必要があり、デリゲート作成式はその変換と同じ方法で処理されます。
- E がデリゲート型の値の場合は、E のメソッド シグネチャが D と一致している (§21.9) 必要があり、結果は、E と同じ呼び出しリストを参照する、型 D の新しく作成されたデリゲートへの参照です。E が D と一致しない場合は、コンパイル時のエラーが発生します。
26.3.3 型推論
注 このセクションは §20.6.4 に代わるものです。
型引数を指定せずにジェネリック メソッドを呼び出すと、型推論の処理により、その呼び出しの型引数の推論が試みられます。型推論が存在することによって、ジェネリック メソッドの呼び出しで、より便利な構文を使用できるようになり、プログラマは重複する型情報を指定しなくて済むようになります。たとえば、次のようにメソッドを宣言したとします。
class Chooser
{
static Random rand = new Random();
public static T Choose<T>(T first, T second) {
return (rand.Next(2) == 0)? first: second;
}
}
この場合、型引数を明示的に指定しなくても、以下のように Choose メソッドを呼び出すことができます。
int i = Chooser.Choose(5, 213); // Choose<int> を呼び出す
string s = Chooser.Choose("foo", "bar"); // Choose<string> を呼び出す
型推論により、メソッドの引数から、型引数が int、string であると決定されます。
型推論は、メソッド呼び出し (§20.9.7) のコンパイル時の処理の一環として実行され、呼び出しのオーバーロードの解決の手順の前に行われます。メソッド呼び出しで特定のメソッド グループを指定し、メソッド呼び出しの一部として型引数を指定しない場合、そのメソッド グループの各ジェネリック メソッドには型推論が適用されます。型推論が成功した場合、その後のオーバーロードの解決では、引数の型を決定するのに、推論された型引数が使用されます。オーバーロードの解決で、呼び出すメソッドとしてジェネリック メソッドが選択された場合、推論された型引数がその呼び出しの実際の型引数として使用されます。特定のメソッドの型推論が失敗した場合は、そのメソッドはオーバーロードの解決に関与しません。型推論が失敗しただけでは、コンパイル時のエラーは発生しませんが、多くの場合、型推論の失敗によりオーバーロードの解決で適切なメソッドが見つからず、コンパイル時のエラーが発生します。
指定した引数の数がメソッドのパラメータの数と異なる場合、推論は直ちに失敗します。それ以外の場合で、ジェネリック メソッドが以下のシグネチャを持つとします。
Tr M<X1...Xn>(T1 x1 ... Tm xm)
M(e1...em) という形式のメソッド呼び出しでは、型推論の役目は、型パラメータ X1...Xn のそれぞれに対して一意の型引数 S1...Sn を突き止め、呼び出し M<S1...Sn>(e1...em) が有効になるようにすることです。
推論処理中に、各型パラメータ Xi は、特定の型 Si に固定されるか、関連付けられた範囲のセットを持ち固定されません。範囲のそれぞれは、ある型 T です。最初は、各型変数 Xi は、範囲の空のセットを持ち固定されていません。
型推論は段階的に行われます。各段階では、前の段階の結果に基づいてより多くの型変数の型引数の推論が試行されます。第 1 段階では、範囲についての最初の推論がいくつか行われ、第 2 段階では、型変数が特定の型に固定され、範囲がさらに推論されます。第 2 段階は、場合によっては、何度も繰り返される必要があります。
注 これ以降、このドキュメント内で "デリゲート型" という表現を使用した場合、これには、Expression<D> という形式 (D はデリゲート型) の型も含まれます。Expression<D> の引数と戻り値の型は、D と同じです。
注 型推論は、ジェネリック メソッドの呼び出し時だけに行われるわけではありません。メソッド グループの変換での型推論については §
26.3.3.12 で、一連の式についての最適な共通の型の突き止め方については §
26.3.3.13 で説明します。
26.3.3.1 第 1 段階
メソッド引数 ei のそれぞれについて、
- ei がラムダ式、匿名メソッド、またはメソッド グループの場合は、Ti 型の ei から、明示的な引数の型推論 (§26.3.3.7) が行われます。
- ei がラムダ式、匿名メソッド、メソッド グループのいずれでもない場合は、Ti 型の ei から、出力の型推論 (§26.3.3.6) が行われます。
26.3.3.2 第 2 段階
Xj に "依存" (§26.3.3.5) しない、固定されていない型変数 Xi がすべて固定されます (§26.3.3.10)。
このような型変数が存在しない場合、固定されていない型変数 Xi のうち、次の両方に該当するものはすべて固定されます。
- Xi に依存する型変数 Xj が少なくとも 1 つ存在します。
- Xi は、空でない範囲のセットを持っています。
このような型変数が存在せず、固定されていない型変数がまだ存在する場合は、型推論は失敗します。固定されていない型変数がそれ以上存在しない場合は、型推論は成功します。それ以外の場合は、対応する引数の型 Ti を持ち、固定されていない型変数 Xj が出力の型 (§26.3.3.4) には含まれているが入力の型 (§26.3.3.3) には含まれていない引数 ei すべてについて、Ti 型の ei に対する、出力の型推論 (§26.3.3.6) が行われます。そして、第 2 段階が繰り返されます。
26.3.3.3 入力の型
e がメソッド グループまたは暗黙的に型指定されたラムダ式であり、T がデリゲート型の場合は、T の引数の型はすべて、T 型の e の入力の型です。
26.3.3.4 出力の型
e がメソッド グループ、匿名メソッド、ステートメント ラムダ、または式ラムダであり、T がデリゲート型の場合は、T の戻り値の型は、T 型の e の出力の型です。
26.3.3.5 依存
Tk 型の、ある引数 ek について、Tk 型の ek の入力の型に Xj があり、Tk 型の ek の出力の型に Xi がある場合、固定されていない型変数 Xi は、固定されていない型変数 Xj に "直接依存" しています。
Xj が Xi に直接依存しているか、Xi が Xk に直接依存していて Xk が Xj に依存している場合、Xj は Xi に "依存" しています。したがって、"依存する" は、"直接依存する" の推移閉包ではありますが、反射閉包ではありません。
26.3.3.6 出力の型推論
T 型の式 e から、以下の方法で出力の型推論が行われます。
- e が、推論される戻り値の型が U (§26.3.3.11) の、ラムダまたは匿名メソッドであり、T が、戻り値の型が Tb のデリゲート型である場合、U から、Tb に対して下限の推論 (§26.3.3.9) が行われます。
- 上記に該当せず、e がメソッド グループであり、T が、パラメータの型が T1...Tk のデリゲート型であり、型 T1...Tk の e のオーバーロードの解決によって、戻り値の型が U のメソッドが 1 つもたらされる場合、U から、Tb に対して下限の推論が行われます。
- 上記のどちらにも該当せず、e が U 型の式の場合、U から、T に対して下限の推論が行われます。
- 上記のいずれにも該当しない場合、推論は行われません。
26.3.3.7 明示的な引数の型推論
T 型の式 e から、以下の方法で明示的な引数の型推論が行われます。
- e が、引数の型が U1...Uk の、明示的に型指定されたラムダ式または匿名メソッドで、T が、パラメータの型が V1...Vk のデリゲート型の場合、各 Ui について、Ui から、対応する Vi に対する厳密な推論 (§26.3.3.8) が行われます。
26.3.3.8 厳密な推論
型 U からの、型 V に対する厳密な推論は、以下のようにして行われます。
- V が、固定されていない Xi の 1 つである場合、Xi の範囲のセットに U が追加されます。
- 上記に該当せず、U が配列型 Ue[...] で、V が同じ次元の配列型 Ve[...] の場合、Ue から、Ve に対する厳密な推論が行われます。
- 上記のどちらにも該当せず、V が構築された型 C<V1...Vk> で、U が構築された型 C<U1...Uk> の場合、各 Ui から、対応する Vi に対する厳密な推論が行われます。
- 上記のいずれにも該当しない場合、推論は行われません。
26.3.3.9 下限の推論
型 U からの、型 V に対する下限の推論は、以下のようにして行われます。
- V が、固定されていない Xi の 1 つである場合、Xi の範囲のセットに U が追加されます。
- 上記に該当せず、U が配列型 Ue[...] で V が同じ次元の配列型 Ve[...] の場合、または U が 1 次元配列型 Ue[] で V が IEnumerable<Ve>、ICollection<Ve>、IList<Ve> のいずれかの場合、
- Ue が参照型であることがわかっている場合は、Ue から、Ve に対する下限の推論が行われます。
- それ以外の場合は、Ue から Ve に対する厳密な推論が行われます。
- 上記のどちらにも該当せず、V が構築された型 C<V1...Vk> であり、U から C<U1...Uk> への標準暗黙変換が存在するような、型の一意のセット U1...Uk が存在する場合は、各 Ui から、対応する Vi に対して厳密な推論が行われます。
- 上記のいずれにも該当しない場合、推論は行われません。
26.3.3.10 固定
範囲のセットを持つ固定されていない型変数 Xi は、以下のように固定されます。
- "候補の型" Uj のセットは、初めは Xi の範囲のセットに含まれるすべての型のセットです。
- 次に、Xi の各範囲を順に調べます。X の各範囲 U について、U からの標準暗黙変換が存在 "しない" 型 Uj はすべて、候補セットから除外されます。
- 残っている候補の型 Uj の中に、他のすべての候補の型への標準暗黙変換が存在する一意の型 V が存在する場合は、Xi は V に固定されます。
- それ以外の場合は、型推論は失敗します。
26.3.3.11 推論される戻り値の型
型推論およびオーバーロードの解決のために、ラムダ式または匿名メソッドである e の推論される戻り値の型は以下のように決定されます。
- e の本体が式の場合は、その式の型が、e の、推論される戻り値の型です。
- e の本体がステートメント ブロックの場合は、そのブロックの return ステートメントの式のセットに、最適な共通の型があり、その型が null 型でないなら、その型が e の推論される戻り値の型です。
- 上記のどちらにも該当しない場合は、e の戻り値の型は推論できません。
ラムダ式が関連する、型推論の例として、以下の、System.Linq.Enumerable クラスで宣言される Select 拡張メソッドを考えてみましょう。
namespace System.Linq
{
public static class Enumerable
{
public static IEnumerable<TResult> Select<TSource,TResult>(
this IEnumerable<TSource> source,
Func<TSource,TResult> selector)
{
foreach (TSource element in source) yield return selector(element);
}
}
}
using 句を使用して System.Linq 名前空間をインポートしたとします。そして、string 型の Name プロパティを持つクラス Customer があるとすると、以下のように、Select メソッドを使用して顧客リストの名前を選択することができます。
List<Customer> customers = GetCustomerList();
IEnumerable<string> names = customers.Select(c => c.Name);
Select の拡張メソッドの呼び出し (§26.2.3) は、以下のように、静的メソッドの呼び出しを書き換えることにより処理されます。
IEnumerable<string> names = Enumerable.Select(customers, c => c.Name);
型引数を明示的に指定しなかったので、型推論を使用して型引数が推論されます。まず、customers 引数が source パラメータに関連付けられ、T は Customer であると推論されます。次に、前述の、ラムダ式の型推論処理を使用して、c の型は Customer であると推論され、式 c.Name が selector パラメータの戻り値の型に関連付けられて、S は string であると推論されます。したがって、呼び出しは以下と同等です。
Sequence.Select<Customer,string>(customers, (Customer c) => c.Name)
また、結果の型は IEnumerable<string> です。
次の例は、ラムダ式の型推論によって、型情報がジェネリック メソッドの呼び出しの引数間でどのように渡すことができるかを示します。以下のメソッドがあるとします。
static Z F<X,Y,Z>(X value, Func<X,Y> f1, Func<Y,Z> f2) {
return f2(f1(value));
}
この場合、次の呼び出し
double seconds = F("1:15:30", s => TimeSpan.Parse(s), t => t.TotalSeconds);
の型推論は、次のように行われます。まず、引数 "1:15:30" が value パラメータに関連付けられ、X は string であると推論されます。次に、1 つ目のラムダ式のパラメータ s の型は string であると推論され、式 TimeSpan.Parse(s) が f1 の戻り値の型に関連付けられて、Y は System.TimeSpan と推論されます。最後に、2 つ目のラムダ式のパラメータ t の型は System.TimeSpan であると推論され、式 t.TotalSeconds が f2 の戻り値の型に関連付けられて、Z は double と推論されます。したがって、呼び出しの結果の型は double です。
26.3.3.12 メソッド グループの変換での型推論
ジェネリック メソッドの呼び出しと同様、ジェネリック メソッドを含むメソッド グループ M が特定のデリゲート型 D に代入される場合にも、型推論が適用される必要があります。次のメソッド
Tr M<X1...Xn>(T1 x1 ... Tm xm)
と、デリゲート型 D に代入されたメソッド グループ M があるとすると、型推論の役目は、次の式
を D に代入可能になるように、型引数 S1...Sn を突き止めることです。
ジェネリック メソッドの呼び出しでの型推論のアルゴリズムとは異なり、この場合は、引数の expressions は存在せず、あるのは引数の types のみです。特に、ラムダ式がないので、複数段階の推論は不要です。
代わりに、すべての Xi は固定されていないと見なされ、D の引数の型 Uj それぞれから、M の対応するパラメータの型 Tj に対する下限の推論が行われます。どの Xi に関しても範囲が見つからない場合は、型推論は失敗します。それ以外の場合は、すべての Xi は、対応する Si (型推論の結果) に固定されます。
26.3.3.13 式のセットについての最適な共通の型を突き止める
場合によっては、式のセットに対する共通の型を推論する必要があります。特に、暗黙的に型指定された配列の要素の型および匿名メソッドやステートメント ラムダの戻り値の型は、この方法で突き止められます。
直感的には、e1...em という一連の式があるとすると、この推論は、引数に ei を指定して次のメソッド
を呼び出すのと同等と考えることができます。
厳密には、推論は、固定されていない型変数 X からスタートし、次に、X 型の ei それぞれから出力の型推論が行われ、最後に、X が固定され、結果として得られた型 S が、式の共通の型となります。
26.3.4 オーバーロードの解決
特定の状況においては、引数リスト内のラムダ式がオーバーロードの解決に影響を与えます。正確な規則については、§7.4.2.3 を参照してください。
次の例は、ラムダがオーバーロードの解決に与える影響を示します。
class ItemList<T>: List<T>
{
public int Sum(Func<T,int> selector) {
int sum = 0;
foreach (T item in this) sum += selector(item);
return sum;
}
public double Sum(Func<T,double> selector) {
double sum = 0;
foreach (T item in this) sum += selector(item);
return sum;
}
}
ItemList<T> クラスには 2 つの Sum メソッドがあります。どちらのメソッドも selector 引数を受け取ります。この引数は、合計する値をリスト項目から抽出します。抽出された値は int の場合と double の場合があり、同様に、合計結果も int または double です。
Sum メソッドは、たとえば、注文の明細行のリストから合計を計算するのに使用することができます。
class Detail
{
public int UnitCount;
public double UnitPrice;
...
}
void ComputeSums() {
ItemList<Detail> orderDetails = GetOrderDetails(...);
int totalUnits = orderDetails.Sum(d => d.UnitCount);
double orderTotal = orderDetails.Sum(d => d.UnitPrice * d.UnitCount);
...
}
ラムダ式 d => d.UnitCount は Func<Detail,int> と Func<Detail,double> の両方と互換性があるので、1 つ目の orderDetails.Sum の呼び出しは、どちらの Sum メソッドにも適用できます。しかし、オーバーロードの解決では、1 つ目の Sum メソッドが選択されます。Func<Detail,int> への変換の方が Func<Detail,double> への変換よりも適切だからです。
ラムダ式 d => d.UnitPrice * d.UnitCount によって生成されるのが double 型の値なので、2 つ目の orderDetails.Sum の呼び出しは、2 つ目の Sum メソッドにしか適用できません。したがって、この呼び出しでは、オーバーロードの解決によって 2 つ目の Sum メソッドが選択されます。
26.4 オブジェクト初期化子とコレクション初期化子
オブジェクト作成式 (§7.5.10.1) には、新規作成されたオブジェクトのメンバまたは新規作成されたコレクションの要素を初期化する、オブジェクト初期化子またはコレクション初期化子が含まれる場合があります。
object-creation-expression:
new type ( argument-listopt) object-or-collection-initializeropt
new type object-or-collection-initializer
object-or-collection-initializer:
object-initializer
collection-initializer
オブジェクト作成式では、オブジェクト初期化子またはコレクション初期化子が含まれる場合は、コンストラクタの引数リスト、およびそれを囲むかっこを省略できます。コンストラクタの引数リスト、およびそれを囲むかっこを省略するということは、空の引数リストを指定することと同等です。
オブジェクト初期化子またはコレクション初期化子が含まれるオブジェクト作成式を実行するには、まずインスタンス コンストラクタを呼び出し、次にオブジェクト初期化子またはコレクション初期化子によって指定されたメンバまたは要素の初期化を実行します。
オブジェクト初期化子またはコレクション初期化子では、初期化中のオブジェクト インスタンスを参照することはできません。
オブジェクト初期化子とコレクション初期化子をジェネリックで正常に解析するには、§20.6.5 に記載されている、あいまいさを解消するトークンのリストを } トークンによって増強する必要があります。
26.4.1 オブジェクト初期化子
オブジェクト初期化子では、オブジェクトの 1 つ以上のフィールドやプロパティの値を指定します。
object-initializer:
{ member-initializer-listopt}
{ member-initializer-list ,}
member-initializer-list:
member-initializer
member-initializer-list , member-initializer
member-initializer:
identifier = initializer-value
initializer-value:
expression
object-or-collection-initializer
オブジェクト初期化子は、{ トークンと } トークンで囲まれコンマで区切られた、一連のメンバ初期化子で構成されます。各メンバ初期化子では、初期化中のオブジェクトの、アクセス可能なフィールドまたはプロパティの名前を指定し、その後に等号と、式、あるいは、オブジェクト初期化子またはコレクション初期化子の一方を指定する必要があります。オブジェクト初期化子に、同じフィールドまたはプロパティのメンバ初期化子を複数含めると、エラーになります。オブジェクト初期化子では、初期化中の新規作成されたオブジェクトを参照することはできません。
等号の後に式が指定されているメンバ初期化子は、フィールドまたはプロパティへの代入 (§7.13.1) と同じ方法で処理されます。
等号の後にオブジェクト初期化子が指定されているメンバ初期化子は、"入れ子になったオブジェクト初期化子"、つまり埋め込みオブジェクトを初期化します。フィールドやプロパティに新しい値を代入するのではなく、入れ子になったオブジェクト初期化子での代入は、フィールドやプロパティのメンバへの代入として扱われます。入れ子になったオブジェクト初期化子は、値型のプロパティや、読み取り専用で値型のフィールドには適用できません。
等号の後にコレクション初期化子が指定されているメンバ初期化子は、組み込みコレクションを初期化します。フィールドやプロパティに新しいコレクションを代入するのではなく、初期化子で指定した要素が、フィールドやプロパティによって参照されるコレクションに追加されます。フィールドやプロパティは、§26.4.2 で説明する要件を満たすコレクション型である必要があります。
次のクラスは、2 つの座標を持つ点を表します。
public class Point
{
int x, y;
public int X { get { return x; } set { x = value; } }
public int Y { get { return y; } set { y = value; } }
}
Point のインスタンスは、次のように作成して初期化することができます。
var a = new Point { X = 0, Y = 1 };
これは、以下と同じ効果を持ちます。
var __a = new Point();
__a.X = 0;
__a.Y = 1;
var a = __a;
__a は、このような方法を使用しなければ隠蔽されアクセスできないテンポラリ変数です。次のクラスは、2 つの点から作成される四角形を表します。
public class Rectangle
{
Point p1, p2;
public Point P1 { get { return p1; } set { p1 = value; } }
public Point P2 { get { return p2; } set { p2 = value; } }
}
Rectangle のインスタンスは、次のように作成して初期化することができます。
var r = new Rectangle {
P1 = new Point { X = 0, Y = 1 },
P2 = new Point { X = 2, Y = 3 }
};
これは、以下と同じ効果を持ちます。
var __r = new Rectangle();
var __p1 = new Point();
__p1.X = 0;
__p1.Y = 1;
__r.P1 = __p1;
var __p2 = new Point();
__p2.X = 2;
__p2.Y = 3;
__r.P2 = __p2;
var r = __r;
__r、__p1、__p2 は、このような方法を使用しなければ隠蔽されアクセスできないテンポラリ変数です。
Rectangle コンストラクタが次のように 2 つの組み込みの Point インスタンスを割り当てるとします。
public class Rectangle
{
Point p1 = new Point();
Point p2 = new Point();
public Point P1 { get { return p1; } }
public Point P2 { get { return p2; } }
}
この場合、新しいインスタンスを割り当てる代わりに、次の構文を使用して、組み込みの Point インスタンスを初期化することができます。
var r = new Rectangle {
P1 = { X = 0, Y = 1 },
P2 = { X = 2, Y = 3 }
};
これは、以下と同じ効果を持ちます。
var __r = new Rectangle();
__r.P1.X = 0;
__r.P1.Y = 1;
__r.P2.X = 2;
__r.P2.Y = 3;
var r = __r;
26.4.2 コレクション初期化子
コレクション初期化子では、コレクションの要素を指定します。
collection-initializer:
{ element-initializer-list }
{ element-initializer-list ,
}
element-initializer-list:
element-initializer
element-initializer-list , element-initializer
element-initializer:
non-assignment-expression
{ expression-list }
コレクション初期化子は、{ トークンと } トークンで囲まれコンマで区切られた、一連の要素初期化子で構成されます。各要素初期化子では、初期化中のコレクション オブジェクトに追加する要素を指定します。また、各要素初期化子は、{ トークンと } トークンで囲まれコンマで区切られた、式のリストで構成されます。式が 1 つだけの要素初期化子は、中かっこなしで記述できますが、そのように記述した場合は代入式にすることはできません。メンバ初期化子とのまぎらわしさを回避するためです。non-assignment-expression の作成については、§26.3 で説明しました。
コレクション初期化子を含むオブジェクト作成式の例を以下に示します。
List<int> digits = new List<int> { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
コレクション初期化子が適用されるコレクション オブジェクトは、System.Collections.IEnumerable を実装する型である必要があります。そうでない場合は、コンパイル時のエラーが発生します。コレクション初期化子は、指定された各要素について順番に、要素初期化子の式リストを使用してターゲット オブジェクトに対して Add メソッドを呼び出し、各呼び出しに対して通常のオーバーロードの解決を適用します。
次のクラスは、名前と電話番号リストで構成された連絡先を表します。
public class Contact
{
string name;
List<string> phoneNumbers = new List<string>();
public string Name { get { return name; } set { name = value; } }
public List<string> PhoneNumbers { get { return phoneNumbers; } }
}
List<Contact> は、次のように作成して初期化することができます。
var contacts = new List<Contact> {
new Contact {
Name = "Chris Smith",
PhoneNumbers = { "206-555-0101", "425-882-8080" }
},
new Contact {
Name = "Bob Harris",
PhoneNumbers = { "650-555-0199" }
}
};
これは、以下と同じ効果を持ちます。
var contacts = new List<Contact>();
var __c1 = new Contact();
__c1.Name = "Chris Smith";
__c1.PhoneNumbers.Add("206-555-0101");
__c1.PhoneNumbers.Add("425-882-8080");
contacts.Add(__c1);
var __c2 = new Contact();
__c2.Name = "Bob Harris";
__c2.PhoneNumbers.Add("650-555-0199");
contacts.Add(__c2);
__c1 と __c2 は、このような方法を使用しなければ隠蔽されアクセスできないテンポラリ変数です。
26.5 匿名型
C# 3.0 では、匿名型のオブジェクトを作成するために、new 演算子を名前のないオブジェクト初期化子と共に使用できます。
primary-no-array-creation-expression:
...
anonymous-object-creation-expression
anonymous-object-creation-expression:
new anonymous-object-initializer
anonymous-object-initializer:
{ member-declarator-listopt}
{ member-declarator-list ,}
member-declarator-list:
member-declarator
member-declarator-list , member-declarator
member-declarator:
simple-name
member-access
identifier = expression
名前のないオブジェクト初期化子では、匿名型を宣言し、その型のインスタンスを返します。匿名型は、object から直接継承する無名のクラス型です。匿名型のメンバは、その型のインスタンスの作成に使用されるオブジェクト初期化子から推論される、一連の、読み取り/書き込みプロパティです。具体的には、
new { p1 = e1 , p2 = e2 , ...pn = en }
という形式の、名前のないオブジェクト初期化子では、以下の形式の、匿名型が宣言されます。
class __Anonymous1
{
private T1 f1 ;
private T2 f2 ;
...
private Tn fn ;
public T1
p1 { get { return f1 ; } set { f1 = value ; } }
public T2
p2 { get { return f2 ; } set { f2 = value ; } }
...
public T1
p1 { get { return f1 ; } set { f1 = value ; } }
}
各 Tx は、対応する式 ex の型です。名前のないオブジェクト初期化子の中の式が null 型や unsafe 型の場合、コンパイル時のエラーが発生します。
匿名型の名前はコンパイラによって自動的に生成され、プログラム テキスト内で参照することはできません。
同じプログラム内の、同じ名前でコンパイル時の型も同じである一連のプロパティを同じ順序で指定する、名前のないオブジェクト初期化子 2 つは、同じ匿名型のインスタンスを生成します (リフレクションなど特定の状況ではプロパティの順序が重要なので、この定義にはプロパティの順序が含まれます)。
次に例を示します。
var p1 = new { Name = "Lawnmower", Price = 495.00 };
var p2 = new { Name = "Shovel", Price = 26.95 };
p1 = p2;
この例では、p1 と p2 は同じ匿名型なので、最後の行の代入は許可されます。
すべてのプロパティが等しい場合のみ、同じ匿名型の 2 つのインスタンスが等しくなるように、匿名型の Equals メソッドと GetHashcode メソッドは、プロパティの Equals と GetHashcode という観点で定義されています。
メンバ宣言子は、簡易名 (§7.5.2) またはメンバ アクセス (§7.5.4) の形で略記することができます。これは "プロジェクション初期化子" と呼ばれ、同じ名前を使用したプロパティの宣言とプロパティへの代入の略記法です。具体的に示すと、
identifier expr . identifier
という形式のメンバ宣言子は、それぞれ、以下とまったく同等です。
identifer = identifier identifier = expr . identifier
したがって、プロジェクション初期化子では、identifier は、値だけでなく、値の代入先のフィールドやプロパティも選択します。直感的には、プロジェクション初期化子は、値だけでなく値の名前も予測します。
26.6 暗黙的に型指定された配列
配列作成式 (§7.5.10.2) の構文は拡張され、暗黙的に型指定された配列作成式がサポートされるようになりました。
array-creation-expression:
...
new[] array-initializer
暗黙的に型指定された配列作成式では、配列インスタンスの型は、配列初期化子で指定された要素から推論されます。具体的には、配列初期化子内の式の型によって形成されるセットには、セットに含まれる各型から暗黙的に変換可能な型が 1 つだけ含まれている必要があり、その型が null 型でない場合、その型の配列が作成されます。型が 1 つだけ推論できなかった場合や、推論された型が null 型の場合は、コンパイル時のエラーが発生します。
暗黙的に型指定された配列作成式の例を以下に示します。
var a = new[] { 1, 10, 100, 1000 }; // int[]
var b = new[] { 1, 1.5, 2, 2.5 }; // double[]
var c = new[] { "hello", null, "world” }; // string[]
var d = new[] { 1, "one", 2, "two" }; // エラー
最後の式では、コンパイル時のエラーが発生します。int と string では、どちらにも暗黙的に変換できないためです。この場合は、明示的に型指定された配列作成式を使用する必要があります。たとえば、型を object[] と指定します。または、要素の 1 つを共通の基本型にキャストするという方法もあります。そうすれば、それが、推論される要素の型になります。
暗黙的に型指定された配列作成式を名前のないオブジェクト初期化子と組み合わせて、匿名型として型指定されたデータ構造を作成することができます。次に例を示します。
var contacts = new[] {
new {
Name = "Chris Smith",
PhoneNumbers = new[] { "206-555-0101", "425-882-8080" }
},
new {
Name = "Bob Harris",
PhoneNumbers = new[] { "650-555-0199" }
}
};
26.7 クエリ式
クエリ式は、SQL や XQuery などのリレーショナル クエリ言語および階層クエリ言語に似た、言語に統合されたクエリ用の構文を提供します。
query-expression:
from-clause query-body
from-clause:
from typeopt identifier in expression
query-body:
query-body-clausesopt select-or-group-clause query-continuationopt
query-body-clauses:
query-body-clause
query-body-clauses query-body-clause
query-body-clause:
from-clause
let-clause
where-clause
join-clause
join-into-clause
orderby-clause
let-clause:
let identifier = expression
where-clause:
where boolean-expression
join-clause:
join typeopt identifier in expression on expression equals expression
join-into-clause:
join typeopt identifier in expression on expression equals expression into identifier
orderby-clause:
orderby orderings
orderings:
ordering
orderings , ordering
ordering:
expression ordering-directionopt
ordering-direction:
ascending
descending
select-or-group-clause:
select-clause
group-clause
select-clause:
select expression
group-clause:
group expression by expression
query-continuation:
into identifier query-body
query-expression は、non-assignment-expression (§26.3 に定義の記載あり) に分類されます。
クエリ式は from 句で始まり、select 句または group 句で終わります。最初の from 句の後には、from 句、let 句、where 句、または join 句を 0 個以上続けることができます。各 from 句は、特定のシーケンスで使用する範囲変数を提起するジェネレータです。各 let 句では、値を計算し、その値を表す識別子を提起します。各 where 句は、結果から項目を除外するフィルタです。各 join 句では、ソース シーケンスの指定されたキーが別のシーケンスのキーと比較され、一致するペアを示します。各 orderby 句では、指定した条件に従って項目を並べ替えます。最後の select 句または group 句では、範囲変数の観点から結果の形態を指定します。最後に、into 句は、1 つのクエリの結果をその次のクエリでのジェネレータとして扱うことにより、クエリを "接合する" のに使用できます。
クエリ式におけるあいまいさ
クエリ式には、新しいコンテキスト キーワード (特定のコンテキストで特別な意味を持つ識別子) がいくつか含まれています。このような新しいコンテキスト キーワードには、from、join、on、equals、into、let、orderby、ascending、descending、select、group、および by があります。クエリ式内でこれらの識別子をキーワードや簡易名としても使用することにより生じるあいまいさを回避するために、これらの識別子は、クエリ式内では常にキーワードと見なされます。
したがって、クエリ式は、from identifier で始まり、その後に ;、=、, 以外のトークンが続くすべての式です。
クエリ式内でこれらの単語を識別子として使用するには、プレフィックス @ を付けるという方法があります (§2.4.2)。
26.7.1 クエリ式の変換
C# 3.0 言語では、クエリ式の正確な実行セマンティクスは指定されません。C# 3.0 では、クエリ式は "クエリ式パターン" に従うメソッドの呼び出しに変換されます。具体的には、クエリ式は、§26.7.2 で説明するように、特定のシグネチャおよび結果の型を持つと予想される、Where、Select、SelectMany、Join、GroupJoin、OrderBy、OrderByDescending、ThenBy、ThenByDescending、GroupBy、および Cast という名前のメソッドの呼び出しに変換されます。これらのメソッドは、クエリ中のオブジェクトのインスタンス メソッドでも、オブジェクトの外部にある拡張メソッドでもかまいません。これらのメソッドは、クエリの実際の実行を実装します。
クエリ式からメソッド呼び出しへの変換は、型バインドやオーバーロードの解決が実行される前に行われる構文マッピングです。変換は構文的に正しいことが保証されますが、変換によって意味的に正しい C# コードが生成されることは保証されません。クエリ式が変換されると、その結果として実行されるメソッド呼び出しは通常のメソッド呼び出しとして処理されますが、これによって今度はエラーが見つかる場合があります。たとえば、メソッドが存在しない場合、引数の型が間違っている場合、メソッドがジェネリックで型推論が失敗した場合などです。
クエリ式は、それ以上の分解が不可能になるまで、変換を繰り返し行うことにより、処理されます。変換は優先度順に並んでおり、各セクションは、前のセクションの変換が完全に実行されたことを前提としています。
特定の種類の変換では、* で表される透過識別子によって範囲変数が挿入されます。透過識別子の特殊な性質については、§26.7.1.7 で説明します。
26.7.1.1 続きのある select 句と groupby 句
という続きのあるクエリ式は、次のように変換されます。
from x in ( from ...) ...
これ以降のセクションにおける変換は、クエリに into という続きがないことを前提としています。
次の例
from c in customers
group c by c.Country into g
select new { Country = g.Key, CustCount = g.Count() }
は、次のように変換されます。
from g in
from c in customers
group c by c.Country
select new { Country = g.Key, CustCount = g.Count() }
これは最終的には次のように変換されます。
customers.
GroupBy(c => c.Country).
Select(g => new { Country = g.Key, CustCount = g.Count() })
26.7.1.2 範囲変数の明示的な型
範囲変数の型を明示的に指定する from 句を以下に示します。
これは、次のように変換されます。
from x in ( e ) . Cast < T > ( )
範囲変数の型を明示的に指定する join 句を以下に示します。
join T x in e on k1 equals k2
これは、次のように変換されます。
join x in ( e ) . Cast < T > ( ) on k1 equals k2
これ以降のセクションにおける変換は、クエリに範囲変数の明示的な型がないことを前提としています。
次の例
from Customer c in customers
where c.City == "London"
select c
は、次のように変換されます。
from c in customers.Cast<Customer>()
where c.City == "London"
select c
これは最終的には次のように変換されます。
customers.
Cast<Customer>().
Where(c => c.City == "London")
範囲変数の明示的な型は、ジェネリックの IEnumerable<T> インターフェイスではなく非ジェネリックの IEnumerable インターフェイスを実装するコレクションをクエリするのには便利です。上記の例では、customers が ArrayList 型の場合は、これに該当します。
26.7.1.3 逆クエリ式
次の形式のクエリ式があるとします。
これは、次のように変換されます。
( e ) . Select ( x => x )
次の例
from c in customers
select c
は、次のように変換されます。
逆クエリ式は、自明のとおり、ソースの要素を選択するクエリ式です。変換の後の段階では、他の変換ステップによって提供された逆クエリは、ソースに置き換えることにより削除されます。ただし、クエリ式の結果がソース オブジェクト自体にならないようにすることは重要です。クエリ式の結果がソース オブジェクト自体の場合、ソースの型と ID がクエリ元に対して開示されてしまうためです。そこで、このステップでは、ソースに対して Select を明示的に呼び出すことにより、ソース コードに直接記述された逆クエリを保護します。これらのメソッドが絶対にソース オブジェクト自体を返さないようにすることは、Select や他のクエリ演算子の実装者の責任になります。
26.7.1.4 from 句、let 句、where 句、join 句、orderby 句
2 番目の from 句の後に select 句が続くクエリ式を以下に示します。
from x1 in e1
from x2 in e2
select v
これは、次のように変換されます。
( e1 ) . SelectMany( x1 => e2 , ( x1 , x2 ) => v )
2 番目の from 句の後に select 句以外のものが続くクエリ式を以下に示します。
from x1 in e1
from x2 in e2
...
これは、次のように変換されます。
from * in ( e1 ) . SelectMany( x1 => e2 , ( x1 , x2 ) => new { x1 , x2 } )
...
let 句のあるクエリ式を以下に示します。
from x in e
let y = f
...
これは、次のように変換されます。
from * in ( e ) . Select ( x => new { x , y = f } )
...
where 句のあるクエリ式を以下に示します。
これは、次のように変換されます。
from x in ( e ) . Where ( x => f )
...
into がない join 句があり、select 句が後に続くクエリ式を以下に示します。
from x1 in e1
join x2 in e2 on k1 equals k2
select v
これは、次のように変換されます。
( e1 ) . Join( e2 , x1 => k1 , x2 => k2 , ( x1 , x2 ) => v )
into がない join 句があり、select 句以外のものが後に続くクエリ式を以下に示します。
from x1 in e1
join x2 in e2 on k1 equals k2
...
これは、次のように変換されます。
from * in ( e1 ) . Join(
e2 , x1 => k1 , x2 => k2 , ( x1 , x2 ) => new { x1 , x2 })
...
into のある join 句があり、select 句が後に続くクエリ式を以下に示します。
from x1 in e1
join x2 in e2 on k1 equals k2 into g
select v
これは、次のように変換されます。
( e1 ) . GroupJoin( e2 , x1 => k1 , x2 => k2 , ( x1 , g ) => v )
into のある join 句があり、select 句以外のものが後に続くクエリ式を以下に示します。
from x1 in e1
join x2 in e2 on k1 equals k2 into g
...
これは、次のように変換されます。
from * in ( e1 ) . GroupJoin(
e2 , x1 => k1 , x2 => k2 , ( x1 , g ) => new { x1 , g })
...
orderby 句のあるクエリ式を以下に示します。
from x in e
orderby k1 , k2 , ... kn,
...
これは、次のように変換されます。
from x in ( e ) .
OrderBy ( x => k1 ) .
ThenBy ( x => k2 ) .
... .
ThenBy ( x => kn )
...
ordering 句で descending 方向インジケータを指定すると、OrderByDescending または ThenByDescending の呼び出しが代わりに生成されます。
これ以降の続く変換は、各クエリ式に、let 句、where 句、join 句、orderby 句は存在せず、最初の from 句が複数は存在しないことを前提としています。
次の例
from c in customers
from o in c.Orders
select new { c.Name, o.OrderID, o.Total }
は、次のように変換されます。
customers.
SelectMany(c => c.Orders,
(c,o) => new { c.Name, o.OrderID, o.Total }
)
次の例
from c in customers
from o in c.Orders
orderby o.Total descending
select new { c.Name, o.OrderID, o.Total }
は、次のように変換されます。
from * in customers.
SelectMany(c => c.Orders, (c,o) => new { c, o })
orderby o.Total descending
select new { c.Name, o.OrderID, o.Total }
これは最終的には次のように変換されます。
customers.
SelectMany(c => c.Orders, (c,o) => new { c, o }).
OrderByDescending(x => x.o.Total).
Select(x => new { x.c.Name, x.o.OrderID, x.o.Total })
x は、このような方法を使用しなければ隠蔽されアクセスできない、コンパイラによって生成された識別子です。
次の例
from o in orders
let t = o.Details.Sum(d => d.UnitPrice * d.Quantity)
where t >= 1000
select new { o.OrderID, Total = t }
は、次のように変換されます。
from * in orders.
Select(o => new { o, t = o.Details.Sum(d => d.UnitPrice * d.Quantity) })
where t >= 1000
select new { o.OrderID, Total = t }
これは最終的には次のように変換されます。
orders.
Select(o => new { o, t = o.Details.Sum(d => d.UnitPrice * d.Quantity) }).
Where(x => x.t >= 1000).
Select(x => new { x.o.OrderID, Total = x.t })
x は、このような方法を使用しなければ隠蔽されアクセスできない、コンパイラによって生成された識別子です。
次の例
from c in customers
join o in orders on c.CustomerID equals o.CustomerID
select new { c.Name, o.OrderDate, o.Total }
は、次のように変換されます。
customers.Join(orders, c => c.CustomerID, o => o.CustomerID,
(c, o) => new { c.Name, o.OrderDate, o.Total })
次の例
from c in customers
join o in orders on c.CustomerID equals o.CustomerID into co
let n = co.Count()
where n >= 10
select new { c.Name, OrderCount = n }
は、次のように変換されます。
from * in customers.
GroupJoin(orders, c => c.CustomerID, o => o.CustomerID,
(c, co) => new { c, co })
let n = co.Count()
where n >= 10
select new { c.Name, OrderCount = n }
これは最終的には次のように変換されます。
customers.
GroupJoin(orders, c => c.CustomerID, o => o.CustomerID,
(c, co) => new { c, co }).
Select(x => new { x, n = x.co.Count() }).
Where(y => y.n >= 10).
Select(y => new { y.x.c.Name, OrderCount = y.n)
x と y は、このような方法を使用しなければ隠蔽されアクセスできない、コンパイラによって生成された識別子です。
次の例
from o in orders
orderby o.Customer.Name, o.Total descending
select o
は、最終的には次のように変換されます。
orders.
OrderBy(o => o.Customer.Name).
ThenByDescending(o => o.Total)
26.7.1.5 select 句
次の形式のクエリ式があるとします。
これは、次のように変換されます。
( e ) . Select ( x => v )
ただし、v が識別子 x の場合は、変換は単に次のようになります。
たとえば、次の例
from c in customers.Where(c => c.City == "London")
select c
は、単に次のように変換されます。
customers.Where(c => c.City == "London")
26.7.1.6 groupby 句
次の形式のクエリ式があるとします。
これは、次のように変換されます。
( e ) . GroupBy ( x => k , x => v )
ただし、v が識別子 x の場合は、変換は次のようになります。
( e ) . GroupBy ( x => k )
次の例
from c in customers
group c.Name by c.Country
は、次のように変換されます。
customers.
GroupBy(c => c.Country, c => c.Name)
26.7.1.7 透過識別子
特定の種類の変換では、* で表される透過識別子によって範囲変数が挿入されます。透過識別子は正式な言語機能ではなく、単にクエリ式変換プロセスにおける中間ステップとして存在するものです。
クエリの変換により透過識別子が挿入されると、その後の変換ステップにより、透過識別子がラムダ式および名前のないオブジェクト初期化子に反映されます。これらのコンテキストで、透過識別子は次のように動作します。
- 透過識別子がラムダ式のパラメータとして存在する場合、関連する匿名型のメンバは、自動的にラムダ式の本体の中のスコープに入ります。
- 透過識別子を持つメンバがスコープ内にある場合、そのメンバもスコープ内にあるということになります。
- 透過識別子が、名前のないオブジェクト初期化子のメンバ宣言子として存在する場合、透過識別子を持つメンバが提供されます。
次の例
from c in customers
from o in c.Orders
orderby o.Total descending
select new { c.Name, o.Total }
は、次のように変換されます。
from * in
from c in customers
from o in c.Orders
select new { c, o }
orderby o.Total descending
select new { c.Name, o.Total }
これはさらに、次のように変換されます。
customers.
SelectMany(c => c.Orders.Select(o => new { c, o })).
OrderByDescending(* => o.Total).
Select(* => new { c.Name, o.Total })
これは、透過識別子が消去されると、以下と同等になります。
customers.
SelectMany(c => c.Orders.Select(o => new { c, o })).
OrderByDescending(x => x.o.Total).
Select(x => new { x.c.Name, x.o.Total })
x は、このような方法を使用しなければ隠蔽されアクセスできない、コンパイラによって生成された識別子です。
次の例
from c in customers
join o in orders on c.CustomerID equals o.CustomerID
join d in details on o.OrderID equals d.OrderID
join p in products on d.ProductID equals p.ProductID
select new { c.Name, o.OrderDate, p.ProductName }
は、次のように変換されます。
from * in
from * in
from * in
from c in customers
join o in orders o c.CustomerID equals o.CustomerID
select new { c, o }
join d in details on o.OrderID equals d.OrderID
select new { *, d }
join p in products on d.ProductID equals p.ProductID
select new { *, p }
select new { c.Name, o.OrderDate, p.ProductName }
これはさらに、次のように分解されます。
customers.
Join(orders, c => c.CustomerID, o => o.CustomerID,
(c, o) => new { c, o }).
Join(details, * => o.OrderID, d => d.OrderID,
(*, d) => new { *, d }).
Join(products, * => d.ProductID, p => p.ProductID,
(*, p) => new { *, p }).
Select(* => new { c.Name, o.OrderDate, p.ProductName })
これは最終的には次のように変換されます。
customers.
Join(orders, c => c.CustomerID, o => o.CustomerID,
(c, o) => new { c, o }).
Join(details, x => x.o.OrderID, d => d.OrderID,
(x, d) => new { x, d }).
Join(products, y => y.d.ProductID, p => p.ProductID,
(y, p) => new { y, p }).
Select(z => new { z.y.x.c.Name, z.y.x.o.OrderDate, z.p.ProductName })
x、y、z は、このような方法を使用しなければ隠蔽されアクセスできない、コンパイラによって生成された識別子です。
26.7.2 クエリ式パターン
クエリ式パターンでは、クエリ式をサポートするために型が実装できるメソッドのパターンが規定されています。クエリ式は構文マッピングによりメソッド呼び出しに変換されるので、クエリ式パターンの実装方法に関して、型は非常に柔軟です。たとえば、インスタンス メソッドも拡張メソッドも呼び出し構文が同じなので、パターンのメソッドは、インスタンス メソッドと拡張メソッドのどちらとしても実装できます。また、ラムダ式はデリゲートにも式のツリーにも変換可能なので、メソッドはデリゲートと式のツリーのどちらも要求できます。
クエリ式パターンをサポートするジェネリック型 C<T> の、推奨される形態を以下に示します。パラメータと結果の型との適切な関係を示すためにジェネリック型を使用していますが、非ジェネリック型のパターンを実装することも可能です。
delegate R Func<T1,R>(T1 arg1);
delegate R Func<T1,T2,R>(T1 arg1, T2 arg2);
class C
{
public C<T> Cast<T>();
}
class C<T>
{
public C<T> Where(Func<T,bool> predicate);
public C<U> Select<U>(Func<T,U> selector);
public C<U> SelectMany<U,V>(Func<T,C<U>> selector,
Func<T,U,V> resultSelector);
public C<V> Join<U,K,V>(C<U> inner, Func<T,K> outerKeySelector,
Func<U,K> innerKeySelector, Func<T,U,V> resultSelector);
public C<V> GroupJoin<U,K,V>(C<U> inner, Func<T,K> outerKeySelector,
Func<U,K> innerKeySelector, Func<T,C<U>,V> resultSelector);
public O<T> OrderBy<K>(Func<T,K> keySelector);
public O<T> OrderByDescending<K>(Func<T,K> keySelector);
public C<G<K,T>> GroupBy<K>(Func<T,K> keySelector);
public C<G<K,E>> GroupBy<K,E>(Func<T,K> keySelector,
Func<T,E> elementSelector);
}
class O<T> : C<T>
{
public O<T> ThenBy<K>(Func<T,K> keySelector);
public O<T> ThenByDescending<K>(Func<T,K> keySelector);
}
class G<K,T> : C<T>
{
public K Key { get; }
}
上記のメソッドでは、ジェネリック デリゲート型の Func<T1, R> と Func<T1, T2, R> を使用していますが、パラメータや結果の型の関係が同じであれば、他のデリゲート型または式のツリー型も同様に使用できます。
C<T> と O<T> との推奨される関係では、ThenBy メソッドと ThenByDescending メソッドは OrderBy または OrderByDescending の結果に対してのみ使用可能にしていることに注意してください。また、GroupBy の結果の、推奨される形態にも注意してください。これは、シーケンスのシーケンスで、それぞれの内部シーケンスは追加の Key プロパティを持ちます。
標準クエリ演算子 (別の仕様書で説明) を使用すると、System.Collections.Generic.IEnumerable<T> インターフェイスを実装するすべての型にクエリ演算子パターンを実装することができます。
26.8 式のツリー
式のツリーを使用すると、ラムダ式を実行可能コードではなくデータ構造として表現することができます。デリゲート型 D に変換可能なラムダ式は、System.Query.Expression<D> 型の式のツリーにも変換可能です。ラムダ式をデリゲート型に変換すると、実行可能コードが生成されデリゲートによってそのコードが参照されますが、式のツリー型に変換すると、式のツリーのインスタンスを作成するコードが生成されます。式のツリーは、ラムダ式の効率的なメモリ内データ表現で、式の構造をわかりやすく、明白にします。
以下の例は、実行可能コードとしてのラムダ式と式のツリーとしてのラムダ式を示します。Func<int,int> への変換が存在するので、Expression<Func<int,int>> への変換も存在します。
Func<int,int> f = x => x + 1; // コード
Expression<Func<int,int>> e = x => x + 1; // データ
これらの代入に従って、デリゲート f は x + 1 を返すメソッドを参照し、式のツリー e は式 x + 1 を表現するデータ構造を参照します。
26.8.1 オーバーロードの解決
オーバーロードの解決のために、Expression<D> 型に関して特別な規則があります。具体的には、適切さの定義に以下の規則が加わります。
- D1 が D2 よりも適切であれば Expression<D1> は Expression<D2> よりも適切であり、Expression<D1> が Expression<D2> よりも適切なのはその場合に限ります。
注 Expression<D> とデリゲート型との間には、適切さの規則はありません。
26.9 自動的に実装されるプロパティ
多くの場合、プロパティは、以下の例のようにバッキング フィールドの簡単な使用により実装されます。
public Class Point {
private int x;
private int y;
public int X { get { return x; } set { x = value; } }
public int Y { get { return y; } set { y = value; } }
}
自動的に実装される (自動実装の) プロパティを使用すると、このパターンが自動化されます。具体的には、非抽象プロパティ宣言でセミコロン アクセサ本体を保持することができます。両方のアクセサが存在する必要があり、両方にセミコロン本体が必要ですが、それぞれに異なるアクセシビリティ修飾子を指定することができます。プロパティをこのように指定すると、プロパティのバッキング フィールドが自動的に生成され、そのバッキング フィールドへの読み取りや書き込みを行うアクセサが実装されます。バッキング フィールドの名前はコンパイラによって生成され、ユーザーはアクセスできません。
以下の宣言は、上記の例と同等です。
public Class Point {
public int X { get; set; }
public int Y { get; set; }
}
バッキング フィールドにはアクセスできないので、バッキング フィールドへの読み取りや書き込みは、プロパティ アクセサを通じて行う必要があります。つまり、読み取り専用や書き込み専用の自動実装プロパティは、意味がないので許可されないことを意味します。ただし、各アクセサに、異なるアクセス レベルを設定することは可能です。したがって、以下のようにして、プライベート バッキング フィールドを持つ読み取り専用プロパティの効果をまねることができます。
Public class ReadOnlyPoint {
public int X { get; private set; }
public int Y { get; private set; }
public ReadOnlyPoint(int x, int y) { X = x; Y = y; }
}
この制限は、自動実装のプロパティによる、構造体型の明示的な代入は、構造体の標準コンストラクタを使用してのみ実現できるということも意味します。これは、プロパティ自体への代入を行うには、構造体が明示的に代入されている必要があるためです。