次の方法で共有


タプルと匿名型

タプルは、 1 つの構造体内の複数のメンバーに軽量のデータ構造を提供します。 匿名型よりも優先される選択肢です。 タプルは、より優れたパフォーマンスを提供し、分解をサポートし、より柔軟な構文を提供します。

匿名型を使用すると、あらかじめ明示的に型を定義することなく、一連の読み取り専用プロパティを単一のオブジェクトにカプセル化できるので便利です。 コンパイラによって型名が生成され、ソース コード レベルでは使用できません。 コンパイラは、各プロパティの型を推論します。 匿名型は、主に式ツリーのサポートが必要な場合、または参照型を必要とするコードを操作する場合に使用します。

タプルと匿名型

タプルと匿名型の両方を使用すると、名前付き型を定義せずに複数の値をグループ化できます。 ただし、タプルの言語サポートが向上し、より効率的なデータ構造にコンパイルされます。 次の表に主要な相違点を示します。

特徴 匿名型 タプル
タイプ 参照型 (class) 値の種類 (struct)
Performance ヒープの割り当て スタック割り当て (パフォーマンスの向上)
Mutability 読み取り専用プロパティ 変更可能なフィールド
分解 サポートされていません サポートされています
式ツリー サポートされています サポートされていません
アクセス モディファイア internal public
メンバー名 必須または推測された 省略可能(既定の名前として Item1Item2 など)

タプルを使用するタイミング

タプルは次の場合に使用します。

  • スタック割り当てを使用してパフォーマンスを向上する必要があります。
  • 値を個別の変数に分解する必要があります。
  • メソッドから複数の値を返しています。
  • 式ツリーのサポートは必要ありません。

次の例は、タプルが匿名型と同様の機能を、よりクリーンな構文で提供する方法を示しています。

// Tuple with named elements.
var tupleProduct = (Name: "Widget", Price: 19.99M);
Console.WriteLine($"Tuple: {tupleProduct.Name} costs ${tupleProduct.Price}");

// Equivalent example using anonymous types.
var anonymousProduct = new { Name = "Widget", Price = 19.99M };
Console.WriteLine($"Anonymous: {anonymousProduct.Name} costs ${anonymousProduct.Price}");

タプルの分解

タプルを個別の変数に分解できるため、個々のタプル要素を操作する便利な方法が提供されます。 C# では、タプルを分解するいくつかの方法がサポートされています。

static (string Name, int Age, string City) GetPersonInfo()
{
    return ("Alice", 30, "Seattle");
}
// Deconstruct using var for all variables
var (name, age, city) = GetPersonInfo();
Console.WriteLine($"{name} is {age} years old and lives in {city}");
// Output: Alice is 30 years old and lives in Seattle

// Deconstruct with explicit types
(string personName, int personAge, string personCity) = GetPersonInfo();
Console.WriteLine($"{personName}, {personAge}, {personCity}");

// Deconstruct into existing variables
string existingName;
int existingAge;
string existingCity;
(existingName, existingAge, existingCity) = GetPersonInfo();

// Deconstruct and discard unwanted values using the discard pattern (_)
var (name2, _, city2) = GetPersonInfo();
Console.WriteLine($"{name2} lives in {city2}");
// Output: Alice lives in Seattle

分解は、ループとパターン マッチングのシナリオで役立ちます。

var people = new List<(string Name, int Age)>
{
    ("Bob", 25),
    ("Carol", 35),
    ("Dave", 40)
};

foreach (var (personName2, personAge2) in people)
{
    Console.WriteLine($"{personName2} is {personAge2} years old");
}

メソッドの戻り値の型としてのタプル

タプルの一般的な使用例は、メソッドの戻り値のデータ型です。 outパラメーターを定義する代わりに、メソッドの結果をタプルでグループ化できます。 名前がなく、戻り値の型を宣言できないため、メソッドから匿名型を返すことはできません。

次の例では、ディクショナリ検索でタプルを使用して構成範囲を返す方法を示します。

var configLookup = new Dictionary<int, (int Min, int Max)>()
{
    [2] = (4, 10),
    [4] = (10, 20),
    [6] = (0, 23)
};

if (configLookup.TryGetValue(4, out (int Min, int Max) range))
{
    Console.WriteLine($"Found range: min is {range.Min}, max is {range.Max}");
}
// Output: Found range: min is 10, max is 20

このパターンは、成功インジケーターと複数の結果値の両方を返す必要があるメソッドを操作する場合に便利です。 タプルを使用すると、MinMaxなどの汎用名の代わりに名前付きフィールド (Item1Item2) を使用できるため、コードをより読みやすく、自己文書化できます。

匿名型を使用する場合

匿名型は、次の場合に使用します。

  • 式ツリーを使用しています (たとえば、一部の Microsoft Language-Integrated Query (LINQ) プロバイダー)。
  • オブジェクトを参照型にする必要があります。

最も一般的な用例は、別の型のプロパティを使用して匿名型を初期化することです。 次の例では、Product という名前のクラスが存在すると仮定します。 クラス Product には、 Color プロパティと Price プロパティと、関心のないその他のプロパティが含まれます。

class Product
{
    public string? Color { get; init; }
    public decimal Price { get; init; }
    public string? Name { get; init; }
    public string? Category { get; init; }
    public string? Size { get; init; }
}

匿名型宣言は、newと共に演算子で始まります。 この宣言により、Product の 2 つのプロパティだけを使用する新しい型が初期化されます。 匿名型は通常、より少量のデータを返すためにクエリ式の select 句で使用されます。 クエリの詳細については、「C# での LINQ」を参照してください。

匿名型でメンバー名を指定しない場合、コンパイラは匿名型メンバーに初期化に使用されるプロパティと同じ名前を与えます。 前の例で示されているように、式を使用して初期化されるプロパティの名前を指定します。

次の例では、ColorPrice が匿名型のプロパティの名前になっています。 インスタンスは、products型のProduct コレクションの項目です。

var productQuery =
    from prod in products
    select new { prod.Color, prod.Price };

foreach (var v in productQuery)
{
    Console.WriteLine("Color={0}, Price={1}", v.Color, v.Price);
}

匿名型を使用したプロジェクション初期化子

匿名型では プロジェクション初期化子がサポートされています。これにより、メンバー名を明示的に指定せずに、ローカル変数またはパラメーターを直接使用できます。 コンパイラは、変数名からメンバー名を推論します。 この簡略化された構文の例を次に示します。

// Explicit member names.
var personExplicit = new { FirstName = "Kyle", LastName = "Mit" };

// Projection initializers (inferred member names).
var firstName = "Kyle";
var lastName = "Mit";
var personInferred = new { firstName, lastName };

// Both create equivalent anonymous types with the same property names.
Console.WriteLine($"Explicit: {personExplicit.FirstName} {personExplicit.LastName}");
Console.WriteLine($"Inferred: {personInferred.firstName} {personInferred.lastName}");

この簡略化された構文は、多くのプロパティを持つ匿名型を作成する場合に便利です。

var title = "Software Engineer";
var department = "Engineering";
var salary = 75000;

// Using projection initializers.
var employee = new { title, department, salary };

// Equivalent to explicit syntax:
// var employee = new { title = title, department = department, salary = salary };

Console.WriteLine($"Title: {employee.title}, Department: {employee.department}, Salary: {employee.salary}");

次の場合、メンバー名は推論されません。

  • 候補名は、明示的または暗黙的に、同じ匿名型の別のプロパティ メンバーを複製します。
  • 候補名が有効な識別子ではありません (たとえば、スペースや特殊文字が含まれています)。

このような場合は、メンバー名を明示的に指定する必要があります。

ヒント

.NET スタイル規則 IDE0037 を使用し、推論されたメンバー名と明示的なメンバー名のどちらを優先するか強制できます。

クラス、構造体、または別の匿名型のオブジェクトを使用して、フィールドを定義することもできます。 これを行うには、このオブジェクトを保持する変数を使用します。 次の例は、既にインスタンス化されたユーザー定義型を使用する 2 つの匿名型を示しています。 どちらの場合も、匿名型productおよびshipmentshipmentWithBonus フィールドはProduct型であり、各フィールドの既定値が含まれています。 bonus フィールドは、コンパイラによって作成された匿名型です。

var product = new Product();
var bonus = new { note = "You won!" };
var shipment = new { address = "Nowhere St.", product };
var shipmentWithBonus = new { address = "Somewhere St.", product, bonus };

通常、変数の初期化に匿名型を使用する場合は、var を使用することにより、変数を暗黙的に型指定したローカル変数として宣言します。 コンパイラのみが匿名型の基になる名前にアクセスできるため、変数宣言で型名を指定することはできません。 var の詳細については、「暗黙的に型指定されたローカル変数」を参照してください。

次の例に示すように、暗黙的に型指定されたローカル変数と暗黙的に型指定された配列を組み合わせることにより、匿名型の要素の配列を作成できます。

var anonArray = new[] { new { name = "apple", diam = 4 }, new { name = "grape", diam = 1 }};

匿名型はclassから直接派生するobject型であり、object以外の型にキャストすることはできません。 コンパイラは匿名型ごとに名前を提供しますが、アプリケーションではアクセスできません。 共通言語ランタイムから見た場合、匿名型と他の参照型に違いはありません。

アセンブリ内の複数の匿名オブジェクト初期化子が、同じ順序で同じ名前や型を持つプロパティのシーケンスを指定する場合、コンパイラはそれらのオブジェクトを同じ型のインスタンスとして処理します。 これらのオブジェクトは、コンパイラで生成された同一の型情報を共有します。

匿名型では、with 式の形式で非破壊な変化がサポートされます。 この機能を使用すると、1 つ以上のプロパティに新しい値がある匿名型の新しいインスタンスを作成できます。

var apple = new { Item = "apples", Price = 1.35 };
var onSale = apple with { Price = 0.79 };
Console.WriteLine(apple);
Console.WriteLine(onSale);

フィールド、プロパティ、イベント、またはメソッドの戻り値の型を匿名型として宣言することはできません。 同様に、メソッド、プロパティ、コンストラクター、またはインデクサーの仮パラメーターを匿名型として宣言することはできません。 匿名型または匿名型を含むコレクションをメソッドの引数として渡すため、パラメーターを object 型として宣言できます。 ただし、匿名型に object を使用すると、強力な型指定の目的が無効になります。 クエリ結果をメソッドの境界を越えて格納したり渡したりする必要がある場合、匿名型の代わりに、通常の名前の構造体またはクラスの使用を検討してください。

匿名型の Equals メソッドと GetHashCode メソッドは、プロパティの Equals メソッドと GetHashCode メソッドとして定義されています。このため、同じ匿名型の 2 つのインスタンスは、すべてのプロパティが等しい場合のみ等しいとみなされます。

匿名型の アクセシビリティ レベルinternal。 そのため、異なるアセンブリで定義されている 2 つの匿名型は、同じ型ではありません。 したがって、匿名型のインスタンスは、すべてのプロパティが等しい場合でも、異なるアセンブリで定義されている場合は互いに等しくすることはできません。

匿名型は ToString メソッドをオーバーライドし、中かっこで囲まれたすべてのプロパティの名前と ToString 出力を連結します。

var v = new { Title = "Hello", Age = 24 };

Console.WriteLine(v.ToString()); // "{ Title = Hello, Age = 24 }"

こちらも参照ください