C# 3.0 の概要

 

2007 年 3 月

Anders Hejlsberg、Mads Torgersen

適用対象:
   Visual C# 3.0

概要: C# 3.0 ("C# Orcas") の技術的な概要。C# 2.0 上に構築され、より高い順序の機能スタイル クラス ライブラリの作成と使用をサポートするいくつかの言語拡張機能が導入されています。 (38ページ印刷)

内容

はじめに
26.1 暗黙的に型指定されたローカル変数
26.2 拡張メソッド
   26.2.1 拡張メソッドの宣言
   26.2.2 使用可能な拡張メソッド
   26.2.3 拡張メソッドの呼び出し
26.3 ラムダ式
   26.3.1 匿名メソッドとラムダ式の変換
   26.3.2 デリゲート作成式
   26.3.3 型推論
      26.3.3.1 最初のフェーズ
      26.3.3.2 第 2 フェーズ
      26.3.3.3 入力型
      26.3.3.4 出力の種類
      26.3.3.5 依存
      26.3.3.6 出力型の推論
      26.3.3.7 明示的な引数型の推論
      26.3.3.8 正確な推論
      26.3.3.9 下限推論
      26.3.3.10 修正
      26.3.3.11 推論された戻り値の型
      26.3.3.12 メソッド グループの変換のための型推論
      26.3.3.13 一連の式の最も一般的な型を見つける
   26.3.4 オーバーロードの解決
26.4 オブジェクト初期化子とコレクション初期化子
   26.4.1 オブジェクト初期化子
   26.4.2 コレクション初期化子
26.5 匿名型
26.6 暗黙的に型指定された配列
26.7 クエリ式
   26.7.1 クエリ式の変換
      26.7.1.1 継続を含む groupby 句を選択する
      26.7.1.2 明示的な範囲変数型
      26.7.1.3 クエリ式の自動生成
      26.7.1.4 From、let、where、join 句、orderby 句
      26.7.1.5 Select 句
      26.7.1.6 Groupby 句
      26.7.1.7 透過的識別子
   26.7.2 クエリ式パターン
26.8 式ツリー
   26.8.1 オーバーロードの解決
26.9 自動的に実装されるプロパティ

はじめに

この記事には、Visual C# 3.0 に適用されるいくつかの更新プログラムが含まれています。 言語のリリースには、包括的な仕様が付属します。

C# 3.0 ("C# Orcas") では、C# 2.0 上に構築された複数の言語拡張機能が導入され、上位の機能スタイル クラス ライブラリの作成と使用がサポートされています。 拡張機能を使用すると、リレーショナル データベースや XML などのドメインでクエリ言語の表現力が同等の合成 API を構築できます。 拡張機能には、次のものが含まれます。

  • 暗黙的に型指定されたローカル変数。これにより、ローカル変数の型を初期化に使用する式から推論できます。
  • 拡張メソッド。既存の型と構築された型を追加のメソッドで拡張できます。
  • ラムダ式。型の推論とデリゲート型と式ツリーの両方への変換を改善する匿名メソッドの進化です。
  • オブジェクト初期化子。オブジェクトの構築と初期化が容易になります。
  • 匿名型。これはタプル型であり、自動的に推論され、オブジェクト初期化子から作成されます。
  • 暗黙的に型指定された配列。配列初期化子から配列の要素型を推論する配列の作成と初期化の形式です。
  • クエリ式。SQL や XQuery などのリレーショナルおよび階層型クエリ言語に似たクエリの言語統合構文を提供します。
  • 式ツリー。ラムダ式をコード (デリゲート) ではなくデータ (式ツリー) として表現できます。

このドキュメントは、これらの機能の技術的な概要です。 このドキュメントでは、 C# 言語仕様バージョン 1.2 (§1 から §18) と C# 言語仕様バージョン 2.0 (§19 から §25) を参照します。どちらも C# 言語ホーム ページ (https://msdn.microsoft.com/vcsharp/aa336809.aspx) で入手できます。

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;               // Error, no initializer to infer type from
var y = {1, 2, 3};   // Error, collection initializer not permitted
var z = null;        // Error, null type not permitted
var u = x => x + 1;  // Error, lambda expressions do not have a type
var v = v++;         // Error, initializer cannot refer to variable itself

下位互換性のために、ローカル変数宣言で var が型として指定され、 var という名前の型がスコープ内にある場合、宣言はその型を参照します。 var という名前の型は、大文字で始まる型名の確立された規則に違反しているため、このような状況が発生する可能性は低いです。

for ステートメント (§8.8.3) の for初期化子using ステートメント (§8.13) のリソース取得は、暗黙的に型指定されたローカル変数宣言にすることができます。 同様に、 foreach ステートメント (§8.8.4) の反復変数は、暗黙的に型指定されたローカル変数として宣言できます。その場合、反復変数の型は列挙されるコレクションの要素型であると推論されます。 この例では、

int[] numbers = { 1, 3, 5, 7, 9 };
foreach (var n in numbers) Console.WriteLine(n);

n の型は、数値の要素型である int と推定されます。

暗黙的に型指定されたローカル変数宣言を含めることができるのは、local-variable-declarationfor-initializerresource-acquisition、foreach-statement のみです。

26.2 拡張メソッド

拡張メソッド は、インスタンス メソッド構文を使用して呼び出すことができる静的メソッドです。 拡張メソッドでは、追加のメソッドを使用して既存の型および構築型を拡張できます。

メモ 拡張メソッドは、インスタンス メソッドよりも検出が少なく、機能が制限されます。 このような理由から、拡張メソッドは慎重に使用し、インスタンス メソッドが実現不可能または不可能な状況でのみ使用することをお勧めします。 プロパティ、イベント、演算子など、他の種類の拡張メンバーは検討されていますが、現在はサポートされていません。

26.2.1 拡張メソッドの宣言

拡張メソッドは、このキーワード (keyword)をメソッドの最初のパラメーターの修飾子として指定することによって宣言されます。 拡張メソッドは、ジェネリックでない、入れ子になっていない静的クラスでのみ宣言できます。 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;
      }
   }
}

拡張メソッドの最初のパラメーターは、 これ以外の修飾子を持つことはできません。また、パラメーター型をポインター型にすることはできません。

拡張メソッドには、通常の静的メソッドのすべての機能があります。 さらに、インポート後は、インスタンス メソッド構文を使用して拡張メソッドを呼び出すことができます。

26.2.2 使用可能な拡張メソッド

拡張メソッドは、静的クラスで宣言されている場合、またはその名前空間の using-namespace-directives (§9.3.2) を介してインポートされた場合に、名前空間で使用できますusing-namespace-directive は、インポートされた名前空間に含まれる型をインポートするだけでなく、インポートされた名前空間内のすべての静的クラス内のすべての拡張メソッドをインポートします。

実際には、使用可能な拡張メソッドは、最初のパラメーターによって指定され、通常のインスタンス メソッドよりも優先順位が低い型に対して追加のメソッドとして表示されます。 たとえば、上記の例の Acme.Utilities 名前空間が using-namespace-directive と共にインポートされた場合

using Acme.Utilities;

インスタンス メソッド構文を使用して、静的クラス Extensions で拡張メソッドを呼び出すことができます。

string s = "1234";
int i = s.ToInt32();               // Same as Extensions.ToInt32(s)
int[] digits = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
int[] a = digits.Slice(4, 3);      // Same as 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 )

書き換えられたフォームは、識別子が解決される方法を除き、静的メソッド呼び出しとして処理されます。最も近い外側の名前空間宣言から開始し、各外側の名前空間宣言を続行し、それを含むコンパイルユニットで終わると、識別子によって指定された名前で名前空間内のすべての使用可能でアクセス可能な拡張メソッドで構成されるメソッド グループを使用して、書き換えられたメソッド呼び出しを処理しようとします。. このセットから、適用できないすべてのメソッド (§7.4.2.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 メソッドが最初の拡張メソッドよりも優先され、 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 つのパラメーターを持つラムダ式では、パラメーター リストからかっこを省略できます。 言い換えると、フォームのラムダ式

( param ) => expr

省略可能です。

param => expr

ラムダ式の例を次に示します。

x => x + 1                     // Implicitly typed, expression body
x => { return x + 1; }         // Implicitly typed, statement body
(int x) => x + 1               // Explicitly typed, expression body
(int x) => { return x + 1; }   // Explicitly typed, statement body
(x, y) => x * y               // Multiple parameters
() => Console.WriteLine()      // No parameters

一般に、 C# 2.0 仕様の §21 で提供されている匿名メソッドの仕様は、ラムダ式にも適用されます。 ラムダ式は、次の点を除き、匿名メソッドと機能的に似ています。

  • 匿名メソッドを使用すると、パラメーター リストを完全に省略できるため、パラメーターのリストのデリゲート型への変換が可能になります。
  • ラムダ式を使用すると、パラメーター型を省略して推論できますが、匿名メソッドではパラメーター型を明示的に指定する必要があります。
  • ラムダ式の本体には式またはステートメント ブロックを指定できますが、匿名メソッドの本体はステートメント ブロックに限定できます。
  • 式本体を持つラムダ式は、式ツリーに変換できます (§26.8)。

26.3.1 匿名メソッドとラムダ式の変換

メモ このセクションでは§21.3 を置き換えます。

anonymous-method-expressionラムダ式は、特殊な変換規則を持つ値として分類されます。 値には 型はありませんが、互換性のあるデリゲート型に暗黙的に変換できます。 具体的には、デリゲート型 D は、匿名メソッドまたはラムダ式 L と互換性があります。

  • DL のパラメーター数は同じです。
  • L が匿名メソッドシグネチャを含まない匿名メソッドの場合、D のパラメーターにパラメーター修飾子がない限り、D は任意の型の 0 個以上のパラメーターをout持つことができます。
  • L に明示的に型指定されたパラメーター リストがある場合、D の各パラメーターは、L の対応するパラメーターと同じ型と修飾子を持ちます。
  • L が暗黙的に型指定されたパラメーター リストを持つラムダ式の場合、D には ref パラメーターも out パラメーターもありません。
  • Dvoid 戻り値の型があり、L の本体が式である場合、L の各パラメーターに D 内の対応するパラメーターの型が指定されている場合、L の本体はステートメント式 (§8.6) として許可される有効な式 (wrt §7) になります。
  • Dvoid 戻り値の型があり、L の本体がステートメント ブロックの場合、L の各パラメーターに D 内の対応するパラメーターの型が指定されている場合、L の本体は、return ステートメントで式を指定しない有効なステートメント ブロック (wrt §8.2) です。
  • D に void 以外の戻り値の型があり、L の本体が式の場合、L の各パラメーターに D 内の対応するパラメーターの型が指定されている場合、L の本体は有効な式 (wrt §7) であり、戻り値の型 D に暗黙的に変換できます。
  • D に void 以外の戻り値の型があり、L の本体がステートメント ブロックの場合、L の各パラメーターに D 内の対応するパラメーターの型が指定されている場合、L の本体は有効なステートメント ブロック (wrt §8.2) で、到達できないエンドポイントを持ち、各 return ステートメントは、戻り値の型 D に暗黙的に変換可能な式を指定します。

次の例では、ジェネリック デリゲート型 Func<A,R> を使用します。これは、 型 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;      // Error

各ラムダ式のパラメーターと戻り値の型は、ラムダ式が割り当てられている変数の型から決定されます。 最初の代入では、ラムダ式がデリゲート型 Func<int,int に正常に変換されます。これは、x に int> 型が指定されている場合、x + 1int 型に暗黙的に変換可能な有効な式であるためです。同様に、2 番目の代入では、x + 1 (int 型) の結果が暗黙的に double 型に変換できるため、ラムダ式がデリゲート型 Func<int,double> に正常に変換されます。 ただし、3 番目の代入はコンパイル時エラーです。これは、 xdouble 型が指定されている場合、 x + 1 ( double 型) の結果が 暗黙的に int 型に変換できないためです。

26.3.2 デリゲート作成式

メモ このセクションでは§21.10 を置き換えます。

デリゲート作成式 (§7.5.10.3) は拡張され、引数をメソッド グループとして分類される式、匿名メソッドまたはラムダ式として分類される式、またはデリゲート型の値にすることができます。

new D(E) 形式の delegate-creation-expression のコンパイル時処理 (Dデリゲート型E) は、次の手順で構成されます。

  • E がメソッド グループの場合、メソッド グループ変換 (§21.9) は E から D に存在する必要があり、デリゲート作成式はその変換と同じ方法で処理されます。
  • E が匿名メソッドまたはラムダ式の場合は、匿名メソッドまたはラムダ式の変換 (§ 26.3.1) が E から D に存在する必要があり、デリゲート作成式はその変換と同じ方法で処理されます。
  • E がデリゲート型の値の場合、E のメソッド シグネチャは D と一致する (§21.9) 必要があります。結果は、E と同じ呼び出しリストを参照する D 型の新しく作成されたデリゲートへの参照になります。ED と一致しない場合は、コンパイル時エラーが発生します。

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);               // Calls Choose<int>
string s = Chooser.Choose("foo", "bar");      // Calls Choose<string>

型の推論により、型引数 intstring は、引数から メソッドに決定されます。

型推論は、メソッド呼び出しのコンパイル時処理 (§20.9.7) の一部として行われ、呼び出しのオーバーロード解決ステップの前に行われます。 メソッド呼び出しで特定のメソッド グループが指定され、メソッド呼び出しの一部として型引数が指定されていない場合、メソッド グループ内の各ジェネリック メソッドに型推論が適用されます。 型の推論が成功した場合、推論された型引数を使用して、後続のオーバーロード解決のための引数の型が決定されます。 オーバーロードの解決でジェネリック メソッドを呼び出すメソッドとして選択した場合、推論された型引数が呼び出しの実際の型引数として使用されます。 特定のメソッドの型推論が失敗した場合、そのメソッドはオーバーロードの解決に関与しません。 と 自体の型推論の失敗によって、コンパイル時エラーは発生しません。 ただし、多くの場合、オーバーロードの解決で該当するメソッドが見つからない場合、コンパイル時エラーが発生します。

指定された引数の数が メソッド内のパラメーターの数と異なる場合、推論はすぐに失敗します。 それ以外の場合は、ジェネリック メソッドに次のシグネチャがあるとします。

Tr M<X1...Xn>(T1 x1 ... Tm xm)

M(e1... 形式のメソッド呼び出しを使用)em) 型推論のタスクは、一意の型引数 S1... を検索することです。型パラメーター X1...Xn を呼び出すように M<S1...Sn>(e1...em) が有効になります。

推論の過程で、各型パラメーター Xi は、特定の型 Si固定されるか、関連付けられた境界セットで固定解除されます各境界は T 型です。最初に、各型変数 Xi は、空の境界セットで固定解除されます。

型の推論はフェーズで行われます。 各フェーズでは、前のフェーズの結果に基づいて、より多くの型変数の型引数を推論しようとします。 最初のフェーズでは境界の初期推論がいくつか行われますが、2 番目のフェーズでは型変数が特定の型に修正され、さらに境界が推論されます。 2 番目のフェーズは、何度も繰り返す必要がある場合があります。

メモ次の全体でデリゲート型を参照する場合は、式<D という形式の型 (D> はデリゲート型) も含める必要があります。 式<D の引数と戻り値の型は、D>型です。

メモ 型の推論は、ジェネリック メソッドが呼び出されたときだけでなく行われます。 メソッド グループの変換の型推論については§26.3.3.12 で説明されており、式のセットの最も一般的な型の検索については、§26.3.3.13 で説明されています。

26.3.3.1 最初のフェーズ

メソッド引数 ei のそれぞれ について、次の手順を実行します。

  • ei がラムダ式、匿名メソッド、またはメソッド グループである場合、明示的な引数型推論 (§26.3.3.7) はTi を持つ ei から行われます。
  • 出力型の推論 (§26.3.3.6) は、ei がラムダ式、匿名メソッド、またはメソッド グループでない場合、Ti を持つ ei から行われます。

26.3.3.2 第 2 フェーズ

(§26.3.3.5) に依存するすべての固定されていない型変数 XiXj は固定されていません (§26.3.3.10)。

このような型変数が存在しない場合、修正されていない型変数 Xi はすべて固定され、次のすべての変数が保持されます。

  • Xi に依存する型変数 **Xj ** が少なくとも 1 つあります。
  • Xi には、空でない境界セットがあります。

このような型変数が存在せず、まだ修正されていない型変数がある場合、型の推論は失敗します。 それ以上修正されていない型変数が存在しない場合、型の推論は成功します。 それ以外の場合、出力型 (§26.3.3.4) に固定されていない型変数 Xjが含まれているが、入力型 (§26.3.3.3) が含まれていないすべての引数 ei の場合、出力型推論 (§26.3.3.6) は Ti 型の ei に対して行われます。 次に、2 番目のフェーズが繰り返されます。

26.3.3.3 入力型

e がメソッド グループまたは暗黙的に型指定されたラムダ式で、T がデリゲート型の場合、T のすべての引数型は型 T持つe入力型になります。

26.3.3.4 出力の種類

e がメソッド グループ、匿名メソッド、ステートメントラムダ、または式ラムダで、T がデリゲート型の場合、T の戻り値の型は型 T持つe出力型になります。

26.3.3.5 依存

TkXj を持つ引数 ek が Tk 型の入力型 ek で発生し、Xi が Tk 型の ek の出力型で発生する場合、非固定型変数 Xi は、修正されていない型変数Xj直接依存します。

XjXi直接依存するかどうか、またはXiがXkに直接依存し、XkXjに依存するかどうかはXiに依存します。 したがって、"依存" は推移的ですが、"直接依存する" の反射的な閉鎖ではありません。

26.3.3.6 出力型の推論

出力型の推論は、次の方法で型 T を持つ式 e から行われます。

  • e が推論された戻り値の型 U (§26.3.3.11) を持つラムダまたは匿名メソッドで、T が戻り値の型 Tb を持つデリゲート型である場合は、U for Tb から下限推論 (§26.3.3.9) が行われます。
  • それ以外の場合、e がメソッド グループで、T がパラメーター型 T1..を持つデリゲート型である場合。型 T1... を使用した e の Tk とオーバーロードの解決。Tk は戻り値の型 U を持つ 1 つのメソッドを生成し、Tb の場合は U から下限推論が行われます。
  • それ以外の場合、eU 型の式の場合、TU から下限推論が行われます。
  • それ以外の場合、推論は行われません。

26.3.3.7 明示的な引数型の推論

明示的な引数型の推論は、次の方法で型 T を持つ式 e から行われます。

  • e が明示的に型指定されたラムダ式または引数型 U1..を持つ匿名メソッドの場合。UkT は、パラメーター型 V1... を持つデリゲート型です。次に、各 Ui に対して、対応する Vi.Ui から正確な推論 (§26.3.3.8) が行われます

26.3.3.8 正確な推論

V に対する型 U からの正確な推論は、次のように行われます。

  • V が固定されていない Xi の 1 つである場合、UXi の境界セットに追加されます。
  • それ以外の場合、U が配列型 Ue[...] で、V が同じランクの配列型 Ve[...] である場合、Ue から Ve への正確な推論が行われます。
  • それ以外の場合、 V が構築された型 C<V1... の場合。Vk>U は、構築された型 C<U1..です。その後、Uk> は各 Ui から対応する Vi に正確な推論を行 います
  • それ以外の場合、推論は行われません。

26.3.3.9 下限推論

V に対する型 U からの下限推論は、次のように行われます。

  • V が固定されていない Xi の 1 つである場合、UXi の境界セットに追加されます。
  • それ以外の場合、U配列型 Ue[...]、V が同じランクの配列型 Ve[...] である場合、または U が 1 次元配列型 **Ue[]** で、VIEnumerable<Ve>ICollection<Ve、または IList Ve> のいずれかである場合は、次のようになります。><
    • Ue が参照型であることがわかっている場合は、Ue から Ve への下限推論が行われます。
    • それ以外の場合は、 Ue から Ve への正確な推論が行われます。
  • それ以外の場合、V が構築された型 C<V1... の場合。Vk> と型 U1..の一意のセットがあります。標準の暗黙的な変換が U から C<U1.. に存在するように英国。次に、対応>する Vi の各 Ui から正確な推論が行われます。
  • それ以外の場合、推論は行われません。

26.3.3.10 修正

境界のセットを持つ固定されていない型変数 Xi は、次のように 固定 されます。

  • 候補の型Uj のセットは、Xi の境界のセット内のすべての型のセットとして開始されます。
  • 次に、 Xi の各バインドを順番に調べます。 X のバインドされた U ごとに、U からの標準の暗黙的な変換がないすべての型 Uj が候補セットから削除されます。
  • 残りの候補型 Uj の中に、他のすべての候補型への標準的な暗黙的な変換がある一意の型 V がある場合、 XiV に固定されます。
  • それ以外の場合、型の推論は失敗します。

26.3.3.11 推論された戻り値の型

型の推論とオーバーロードの解決のために、ラムダ式or匿名メソッド eの推定戻り値の型は次のように決定されます。

  • e の本体が式の場合、その式の型は e の推定戻り値の型になります
  • e の本体がステートメント ブロックの場合、ブロックの return ステートメント内の式のセットに最も一般的な型があり、その型が null 型でない場合、その型は推定される戻り値の型 L になります。
  • それ以外の場合、戻り値の型は L に対して推論できません。

ラムダ式を含む型推論の例として、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);
      }
   }
}

System.Linq 名前空間が using 句を使用してインポートされ、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 と推論 します。 次に、前述のラムダ式の型推論プロセスを使用して、 cCustomer 型が指定され、式 c.Nameセレクター パラメーターの戻り値の型に関連し、文字列として推論Sされます。 したがって、呼び出しは次と同じです。

Sequence.Select<Customer,string>(customers, (Customer c) => c.Name)

結果は IEnumerable 文字列>型です<

次の例は、ラムダ式の型推論によって、ジェネリック メソッド呼び出しの引数間で型情報を "フロー" できるようにする方法を示しています。 メソッドを指定すると、次のようになります。

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文字列として推論します。 次に、最初のラムダ式 s のパラメーターに推論された型 stringが指定され、 式 TimeSpan.Parse(s)f1 の戻り値の型に関連し、 YSystem.TimeSpan であると推論されます。 最後に、2 番目のラムダ式 t のパラメーターには、推論された型 System.TimeSpan が指定され、式 t.TotalSecondsf2 の戻り値の型に関連し、 Zdouble と推論します。 したがって、呼び出しの結果は double 型になります。

26.3.3.12 メソッド グループの変換のための型推論

ジェネリック メソッドの呼び出しと同様に、ジェネリック メソッドを含むメソッド グループ M が特定のデリゲート型 D に割り当てられている場合にも、型推論を適用する必要があります。指定されたメソッド

Tr M<X1...Xn>(T1 x1 ... Tm xm)

デリゲート型 D に割り当てられているメソッド グループ M の型推論のタスクは、型引数 S1... を検索することです。Sn を使用して式を次に示します。

M<S1...Sn>

D に割り当て可能になります。

ジェネリック メソッド呼び出しの型推論アルゴリズムとは異なり、この場合、引数 の型のみが存在し、引数 はありません。 特に、ラムダ式がないため、推論の複数のフェーズは必要ありません。

代わりに、すべての Xi は修正されていないと見なされ、D の各引数型 Uj から対応するパラメーター型 TjM に下限推論が行われます。Xi の境界のいずれかが見つからなかった場合、型の推論は失敗します。 それ以外の場合、すべての Xi は対応する Si に固定されます。これは型推論の結果です。

26.3.3.13 一連の式の最も一般的な型を見つける

場合によっては、一連の式に対して共通の型を推論する必要があります。 特に、暗黙的に型指定された配列の要素型と、匿名メソッドとステートメント ラムダの戻り値の型は、この方法で見つかります。

直感的に、一連の式 e1...em この推論は、メソッドの呼び出しと同等である必要があります

Tr M<X>(X x1 ... X xm)

引数として 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メソッドがあります。 それぞれが セレクター 引数を受け取り、リスト アイテムから合計する値を抽出します。 抽出された値は 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);
   ...
}

orderDetails.Sum の最初の呼び出しでは、ラムダ式 d => d.UnitCountFunc Detail、int>、Func<<Detail,double> の両方と互換性があるため、両方の Sum メソッドが適用されます。 ただし、オーバーロードの解決では、Func Detail,int> への変換が Func<Detail,double>への変換よりも優れているため、最初の<Sum メソッドが選択されます。

orderDetails.Sum の 2 回目の呼び出しでは、ラムダ式 d => d.UnitPrice * d.UnitCount によって double 型の値が生成されるため、2 番目の Sum メソッドのみが適用されます。 したがって、オーバーロードの解決は、その呼び出しの 2 番目の Sum メソッドを選択します。

26.4 オブジェクト初期化子とコレクション初期化子

オブジェクト作成式 (§7.5.10.1) には、新しく作成されたオブジェクトのメンバーまたは新しく作成されたコレクションの要素を初期化するオブジェクトまたはコレクション初期化子を含めることができます。

object-creation-expression:
new   type   (   argument-listopt)   object-or-collection-initializeroptnew   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) と同じ方法で処理されます。

等号の後にオブジェクト初期化子を指定するメンバー初期化子は 、入れ子になったオブジェクト初期化子 (つまり、埋め込みオブジェクトの n 個の初期化) です。 新しい値をフィールドまたはプロパティに割り当てる代わりに、入れ子になったオブジェクト初期化子の割り当ては、フィールドまたはプロパティのメンバーへの割り当てとして扱われます。 入れ子になったオブジェクト初期化子は、値型を持つプロパティや、値型を持つ読み取り専用フィールドには適用できません。

等号の後にコレクション初期化子を指定するメンバー初期化子は、埋め込みコレクションの初期化です。 フィールドまたはプロパティに新しいコレクションを割り当てる代わりに、初期化子で指定された要素が、フィールドまたはプロパティによって参照されるコレクションに追加されます。 フィールドまたはプロパティは、§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   }

コレクション初期化子は、 { および } トークンで囲まれた一連の要素初期化子で構成され、コンマで区切られます。 各要素初期化子は、初期化対象のコレクション オブジェクトに追加する要素を指定し、 { および } トークンで囲まれた式の一覧で構成され、コンマで区切られます。 単一式要素初期化子は中かっこなしで記述できますが、メンバー初期化子のあいまいさを避けるために代入式にすることはできません。 代入式以外の実稼働は§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; } }
}

リスト<連絡先>は、次のように作成および初期化できます。

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 T1f1 ;
   private T2f2 ;
   ...
   private Tnfn ;
   public T1p1 { get { return f1 ; } set { f1 = value ; } }
   public T2p2 { get { return f2 ; } set { f2 = value ; } }
   ...
   public T1p1 { get { return f1 ; } set { f1 = value ; } }
}

ここで、各 Tx は対応する式の型です。 匿名オブジェクト初期化子の式が null 型または安全でない型である場合は、コンパイル時エラーです。

匿名型の名前はコンパイラによって自動的に生成され、プログラム テキストでは参照できません。

同じプログラム内で、同じ名前とコンパイル時の型の一連のプロパティを同じ順序で指定する 2 つの匿名オブジェクト初期化子は、同じ匿名型のインスタンスを生成します。 (この定義には、プロパティの順序が含まれます。これは、反射など、特定の状況では観測可能であり、材料であるためです)。

この例では、

var p1 = new { Name = "Lawnmower", Price = 495.00 };
var p2 = new { Name = "Shovel", Price = 26.95 };
p1 = p2;

p1p2 は同じ匿名型であるため、最後の行の割り当ては許可されます。

匿名型 の Equals メソッドと GetHashcode メソッドは、プロパティの EqualsGetHashcode の観点から定義されます。したがって、同じ匿名型の 2 つのインスタンスが、すべてのプロパティが等しい場合にのみ等しくなります。

メンバー宣言子は、単純な名前 (§7.5.2) またはメンバー アクセス (§7.5.4) に省略できます。 これは プロジェクション初期化子 と呼ばれ、 の宣言と、同じ名前のプロパティへの割り当ての短縮形です。 具体的には、フォームのメンバー宣言子

identifier                        expr . identifier

は、それぞれ次と正確に同じです。

identifer = identifieridentifier = expr . 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" };         // Error

最後の式では、 intstring も暗黙的に他方に変換できないため、コンパイル時エラーが発生します。 この場合は、明示的に型指定された配列作成式を使用する必要があります。たとえば、 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

クエリ式非代入式として分類され、その定義は §26.3 で発生します。

クエリ式は from 句で始まり、 select 句または group 句で終わります。 最初の from 句の後に 0 個以上の from 句、 let 句、 where 句、または join 句を 指定 できます。 各 from 句は、シーケンスにわたる範囲変数を導入するジェネレーターです。 各 let 句は値を計算し、その値を表す識別子を導入します。 各 where 句は、結果から項目を除外するフィルターです。 各 結合 句は、ソース シーケンスの指定されたキーを別のシーケンスのキーと比較し、一致するペアを生成します。 各 orderby 句は、指定された条件に従ってアイテムを並べ替えます。 最後の select 句または group 句は、範囲変数の観点から結果の形状を指定します。 最後に、 into 句を使用して、1 つのクエリの結果を後続のクエリのジェネレーターとして扱うことで、クエリを "スプライス" できます。

クエリ式のあいまいさ

クエリ式には、特定のコンテキストで特別な意味を持つ識別子など、新しいコンテキスト キーワードが多数含まれています。 具体的には、 fromjoinonequalsintoletorderbyascendingdescendingselectgroup and by です。 これらの識別子をキーワードまたはクエリ式の単純な名前として混在して使用することによって生じるあいまいさを回避するために、クエリ式内の任意の場所でキーワードと見なされます。

この目的のために、クエリ式は、"fromidentifier" で始まり、その後に ";"、""、=または "," を除く任意のトークンが続く任意の式です。

これらの単語をクエリ式内の識別子として使用するには、プレフィックスとして "@" (§2.4.2) を付けることができます。

26.7.1 クエリ式の変換

C# 3.0 言語では、クエリ式の正確な実行セマンティクスは指定されていません。 代わりに、C# 3.0 はクエリ式を クエリ式パターンに準拠するメソッドの呼び出しに変換します。 具体的には、クエリ式 は、§26.7.2 で説明されているように、Where、 SelectSelectManyJoinGroupJoinOrderByOrderByDescendingThenByThenByDescendingGroupByおよび Cast という名前のメソッドの呼び出しに変換されます。 これらのメソッドは、クエリ対象のオブジェクトのインスタンス メソッド、またはオブジェクトの外部にある拡張メソッドであり、クエリの実際の実行を実装します。

クエリ式からメソッド呼び出しへの変換は、型バインディングまたはオーバーロードの解決が実行される前に発生する構文マッピングです。 翻訳は構文的に正しいことが保証されますが、意味的に正しい C# コードを生成することは保証されません。 クエリ式の翻訳の後、結果のメソッド呼び出しは通常のメソッド呼び出しとして処理されます。これにより、メソッドが存在しない場合、引数に間違った型がある場合、メソッドがジェネリックで型の推論が失敗した場合など、エラーが発生する可能性があります。

クエリ式は、これ以上削減できないまで、次の翻訳を繰り返し適用することによって処理されます。 翻訳は優先順位順に一覧表示されます。各セクションでは、前のセクションの翻訳が完全に実行されていることを前提としています。

特定の翻訳では、 によって*示される透過的な識別子を持つ範囲変数が挿入されます。 透過的な識別子の特別なプロパティについては、§26.7.1.7 で詳しく説明します。

26.7.1.1 継続を含む groupby 句を選択する

継続を含むクエリ式

from ... into x...

は に変換されます

from x in ( from ... ) ...

次のセクションの翻訳では、クエリ に継続がないことを 前提としています。

例を示します。

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 Tx in e

は に変換されます

from x in ( e ) . Cast < T > ( )

範囲変数型を明示的に指定する join

join Tx 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 インターフェイスを実装するコレクションに対してクエリを実行する場合に便利ですが、ジェネリック IEnumerable<T> インターフェイスは実装しません。 上記の例では、 顧客ArrayList 型の場合は、このケースになります。

26.7.1.3 クエリ式の自動生成

フォームのクエリ式

from x in e select x

は に変換されます

( e ) . Select ( x => x )

例を示します。

from c in customers
select c

に変換されます

customers.Select(c => 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 f...

は に変換されます

from x in ( e ) . Where ( x => f )
...

を含まない句を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 )

を含まない句を 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 })
...

selectが続くjoin 句を含むクエリ式

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 )

select 句以外の句がjoinく句をintoむクエリ式

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 )
...

順序句で 降順 インジケーターが指定されている場合は、代わりに OrderByDescending または ThenByDescending の呼び出しが生成されます。

次の変換 では、let 句、 where 句、 join 句、 または orderby 句がなく、各クエリ式に最初の from 句が 1 つ以下であることを前提としています。

例を示します。

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)

ここで、xy はコンパイラによって生成される識別子であり、それ以外の場合は非表示でアクセスできません。

例を示します。

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 句

フォームのクエリ式

from x in e select v

は に変換されます

( e ) . Select ( x => v )

v が識別子 x の場合を除き、変換は単純に

( e )

次に例を示します。

from c in customers.Where(c => c.City == "London")
select c

は単に に変換されます

customers.Where(c => c.City == "London")

26.7.1.6 Groupby 句

フォームのクエリ式

from x in e group v by k

は に変換されます

( 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 })

ここで、 xyz はコンパイラによって生成される識別子であり、それ以外の場合は非表示でアクセスできません。

26.7.2 クエリ式パターン

クエリ式パターンは、型がクエリ式をサポートするために実装できるメソッドのパターンを確立します。 クエリ式は構文マッピングによってメソッド呼び出しに変換されるため、型はクエリ式パターンの実装方法に大きな柔軟性を持ちます。 たとえば、パターンのメソッドは、インスタンス メソッドまたは拡張メソッドとして実装できます。これは、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> への変換が存在するため、式<Func<int,int への変換も存在します>>

Func<int,int> f = x => x + 1;                  // Code
Expression<Func<int,int>> e = x => x + 1;      // Data

これらの代入の後、デリゲート fx + 1 を返すメソッドを参照し、式ツリー e は式 x + 1 を記述するデータ構造を参照します。

26.8.1 オーバーロードの解決

オーバーロードの解決を目的として、 式<D> 型に関する特別な規則があります。 具体的には、次の規則が、より良さの定義に追加されます。

  • 式<D1> が D2 より優れている場合にのみ、式<D2> よりも D1 の方が優れています

式<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; }
}

また、この制限は、自動実装プロパティを持つ構造体型の明確な割り当ては、構造体の標準コンストラクターを使用してのみ実現できることを意味します。これは、プロパティ自体に割り当てるには構造体が確実に割り当てられる必要があるためです。