匿名型 (Visual Basic)

Visual Basic では匿名型がサポートされています。これを使用すると、データ型のクラス定義を記述せずにオブジェクトを作成できます。 クラスは、コンパイラによって生成されます。 このクラスには使用可能な名前がなく、Object から直接継承され、オブジェクトの宣言時に指定したプロパティが格納されます。 データ型の名前を指定しないため、匿名型と呼ばれます。

次の例では、NamePrice の 2 つのプロパティを持つ匿名型のインスタンスとして、変数 product を宣言して作成します。

' Variable product is an instance of a simple anonymous type.
Dim product = New With {Key .Name = "paperclips", .Price = 1.29}

クエリ式は、匿名型を使用して、クエリによって選択されたデータの列を結合します。 特定のクエリによって選択される可能性のある列を予測できないため、結果の型を事前に定義することはできません。 匿名型を使用すると、任意の数の列を任意の順序で選択するクエリを記述できます。 コンパイラは、指定されたプロパティと指定された順序に一致するデータ型を作成します。

次の例では、products は Product オブジェクトの一覧であり、それぞれに多くのプロパティがあります。 変数 namePriceQuery は、実行されると NamePrice の 2 つのプロパティを持つ匿名型のインスタンスのコレクションを返すクエリの定義を保持します。

Dim namePriceQuery = From prod In products
                     Select prod.Name, prod.Price

変数 nameQuantityQuery は、実行されると NameOnHand の 2 つのプロパティを持つ匿名型のインスタンスのコレクションを返すクエリの定義を保持します。

Dim nameQuantityQuery = From prod In products
                        Select prod.Name, prod.OnHand

コンパイラによって作成される匿名型のコードの詳細については、「匿名型の定義」を参照してください。

注意事項

匿名型の名前はコンパイラによって生成され、コンパイルごとに異なる可能性があります。 プロジェクトが再コンパイルされるときに名前が変更される可能性があるため、コードでは、匿名型の名前を使用したり、それに依存したりしないでください。

匿名型の宣言

匿名型のインスタンスの宣言では、初期化子リストを使用して型のプロパティを指定します。 メソッドやイベントなどの他のクラス要素ではなく、匿名型を宣言する場合は、プロパティのみを指定できます。 次の例では、product1 は、NamePrice の 2 つのプロパティを持つ匿名型のインスタンスです。

' Variable product1 is an instance of a simple anonymous type.
Dim product1 = New With {.Name = "paperclips", .Price = 1.29}
' -or-
' product2 is an instance of an anonymous type with key properties.
Dim product2 = New With {Key .Name = "paperclips", Key .Price = 1.29}

プロパティをキー プロパティとして指定した場合は、これらを使用して、2 つの匿名型インスタンスが等しいかどうかを比較できます。 ただし、キー プロパティの値は変更できません。 詳細については、このトピックで後述する「キー プロパティ」のセクションを参照してください。

匿名型のインスタンスの宣言は、オブジェクト初期化子を使用して名前付きの型のインスタンスを宣言するのと似ていることに注意してください。

' Variable product3 is an instance of a class named Product.
Dim product3 = New Product With {.Name = "paperclips", .Price = 1.29}

匿名型のプロパティを指定するその他の方法の詳細については、「方法: 匿名型の宣言におけるプロパティ名と型を推論する」を参照してください。

キー プロパティ

キー プロパティは、次のいくつかの基本的な点で、キー以外のプロパティとは異なります。

  • 2 つのインスタンスが等しいかどうかを確認するためには、キー プロパティの値のみが比較されます。

  • キー プロパティの値は読み取り専用であり、変更することはできません。

  • コンパイラによって生成された匿名型のハッシュ コード アルゴリズムには、キー プロパティ値のみが含まれます。

等価比較

匿名型のインスタンスは、同じ匿名型のインスタンスである場合にのみ等しくすることができます。 コンパイラは、次の条件を満たす場合、2 つのインスタンスを同じ型のインスタンスとして扱います。

  • これらは同じアセンブリ内で宣言されています。

  • これらのプロパティは同じ名前を持ち、推論される型は同じであり、同じ順序で宣言されています。 名前の比較では、大文字と小文字は区別されません。

  • それぞれに含まれる同じプロパティは、キー プロパティとしてマークされます。

  • 各宣言の少なくとも 1 つのプロパティがキー プロパティです。

キー プロパティを持たない匿名型のインスタンスは、それ自体とのみ等しくなります。

' prod1 and prod2 have no key values.
Dim prod1 = New With {.Name = "paperclips", .Price = 1.29}
Dim prod2 = New With {.Name = "paperclips", .Price = 1.29}

' The following line displays False, because prod1 and prod2 have no
' key properties.
Console.WriteLine(prod1.Equals(prod2))

' The following statement displays True because prod1 is equal to itself.
Console.WriteLine(prod1.Equals(prod1))

同じ匿名型の 2 つのインスタンスは、それらのキー プロパティの値が等しい場合に等しいとされます。 次の例は、等価性のテスト方法を示しています。

Dim prod3 = New With {Key .Name = "paperclips", Key .Price = 1.29}
Dim prod4 = New With {Key .Name = "paperclips", Key .Price = 1.29}
' The following line displays True, because prod3 and prod4 are
' instances of the same anonymous type, and the values of their
' key properties are equal.
Console.WriteLine(prod3.Equals(prod4))

Dim prod5 = New With {Key .Name = "paperclips", Key .Price = 1.29}
Dim prod6 = New With {Key .Name = "paperclips", Key .Price = 1.29,
                      .OnHand = 423}
' The following line displays False, because prod5 and prod6 do not 
' have the same properties.
Console.WriteLine(prod5.Equals(prod6))

Dim prod7 = New With {Key .Name = "paperclips", Key .Price = 1.29,
                      .OnHand = 24}
Dim prod8 = New With {Key .Name = "paperclips", Key .Price = 1.29,
                      .OnHand = 423}
' The following line displays True, because prod7 and prod8 are
' instances of the same anonymous type, and the values of their
' key properties are equal. The equality check does not compare the
' values of the non-key field.
Console.WriteLine(prod7.Equals(prod8))

読み取り専用の値

キー プロパティの値は変更できません。 たとえば、前の例の prod8 では、Name フィールドと Price フィールドは read-only ですが、OnHand は変更できます。

' The following statement will not compile, because Name is a key
' property and its value cannot be changed.
' prod8.Name = "clamps"

' OnHand is not a Key property. Its value can be changed.
prod8.OnHand = 22

クエリ式からの匿名型

クエリ式では、必ずしも匿名型を作成する必要はありません。 可能な場合は、既存の型を使用して列データを保持します。 これは、クエリがデータ ソースからレコード全体を返すか、または各レコードのフィールドを 1 つだけ返す場合に行われます。 次のコード例では、customersCustomer クラスのオブジェクトのコレクションです。 クラスには多くのプロパティがあり、クエリの結果には、任意の順序で 1 つ以上のプロパティを含めることができます。 最初の 2 つの例では、クエリで名前付きの型の要素が選択されるため、匿名型は必要ありません。

  • custs1 には文字列のコレクションが含まれます。cust.Name が文字列であるためです。

    Dim custs1 = From cust In customers
                 Select cust.Name
    
  • custs2 には Customer オブジェクトのコレクションが含まれます。customers の各要素が Customer オブジェクトであり、要素全体がクエリによって選択されるためです。

    Dim custs2 = From cust In customers
                 Select cust
    

ただし、適切な名前付きの型が常に使用可能とは限りません。 ある目的のために顧客の名前と住所を、別の目的のために顧客の ID 番号と場所を、そして 3 つ目の目的のために顧客の名前、住所、および注文履歴を選択することができます。 匿名型を使用すると、プロパティの任意の組み合わせを任意の順序で選択でき、最初に結果を保持するために新しい名前付きの型を宣言する必要はありません。 代わりに、コンパイラは、プロパティのコンパイルごとに匿名型を作成します。 次のクエリでは、customers の各 Customer オブジェクトから、顧客の名前と ID 番号のみを選択します。 そのため、コンパイラは、それら 2 つのプロパティのみが含まれている匿名型を作成します。

Dim custs3 = From cust In customers
             Select cust.Name, cust.ID

匿名型のプロパティの名前とデータ型は両方とも、Selectcust.Name、および cust.ID の引数から取得されます。 クエリによって作成される匿名型のプロパティは、常にキー プロパティです。 次の For Each ループで custs3 を実行すると、結果は NameID の 2 つのキー プロパティを持つ匿名型のインスタンスのコレクションになります。

For Each selectedCust In custs3
    Console.WriteLine(selectedCust.ID & ": " & selectedCust.Name)
Next

custs3 によって表されるコレクション内の要素は厳密に型指定されます。IntelliSense を使用して、使用可能なプロパティ間を移動したり、その型を検証したりできます。

詳細については、「Visual Basic における LINQ の概要」を参照してください。

匿名型を使用するかどうかの決定

オブジェクトを匿名クラスのインスタンスとして作成する前に、それが最適な選択肢かどうかを検討してください。 たとえば、関連データを格納する一時オブジェクトを作成する場合に、完全なクラスに含まれる可能性のある他のフィールドやメソッドが不要な場合は、匿名型が適切な解決策です。 匿名型は、宣言ごとに異なるプロパティを選択する場合や、プロパティの順序を変更する場合にも便利です。 ただし、プロジェクトに同じプロパティを持つ複数のオブジェクトが一定の順序で含まれている場合は、クラス コンストラクターを持つ名前付きの型を使用して、より簡単に宣言することができます。 たとえば、適切なコンストラクターを使用すると、匿名型の複数のインスタンスを宣言するよりも、Product クラスの複数のインスタンスを宣言する方が簡単になります。

' Declaring instances of a named type.
Dim firstProd1 As New Product("paperclips", 1.29)
Dim secondProd1 As New Product("desklamp", 28.99)
Dim thirdProd1 As New Product("stapler", 5.09)

' Declaring instances of an anonymous type.
Dim firstProd2 = New With {Key .Name = "paperclips", Key .Price = 1.29}
Dim secondProd2 = New With {Key .Name = "desklamp", Key .Price = 28.99}
Dim thirdProd2 = New With {Key .Name = "stapler", Key .Price = 5.09}

名前付きの型のもう 1 つの利点は、コンパイラがプロパティ名の誤入力をキャッチできることです。 前の例では、firstProd2secondProd2、および thirdProd2 は、同じ匿名型のインスタンスとして意図されています。 ただし、次のいずれかの方法で誤って thirdProd2 を宣言すると、その型が firstProd2secondProd2 とは異なることになります。

' Dim thirdProd2 = New With {Key .Name = "stapler", Key .Price = 5.09}
' Dim thirdProd2 = New With {Key .Name = "stapler", Key .Price = "5.09"}
' Dim thirdProd2 = New With {Key .Name = "stapler", .Price = 5.09}

さらに重要な点として、匿名型の使用には、名前付きの型のインスタンスには適用されない制限があります。 firstProd2secondProd2thirdProd2 は、同じ匿名型のインスタンスです。 ただし、共有されている匿名型の名前は使用できません。また、コードで型名を指定する場所を指定することはできません。 たとえば、匿名型を使用して、メソッド シグネチャを定義したり、別の変数やフィールドを宣言したり、任意の型宣言で宣言したりすることはできません。 そのため、メソッド間で情報を共有する必要がある場合、匿名型は適切ではありません。

匿名型の定義

匿名型のインスタンスの宣言に応答して、コンパイラは、指定されたプロパティを含む新しいクラス定義を作成します。

匿名型に少なくとも 1 つのキー プロパティが含まれている場合、Object: EqualsGetHashCode、および ToString から継承された 3 つのメンバーは、この定義によってオーバーライドされます。 等価性をテストし、ハッシュ コード値を決定するために生成されたコードは、キー プロパティのみを考慮します。 匿名型にキー プロパティが含まれていない場合は、ToString のみがオーバーライドされます。 匿名型の明示的に名前が付けられたプロパティは、生成されたこれらのメソッドと競合しません。 つまり、.Equals.GetHashCode、または .ToString を使用してプロパティの名前を指定することはできません。

少なくとも 1 つのキー プロパティを持つ匿名型定義は、System.IEquatable<T> インターフェイスも実装します。ここで T は匿名型の型です。

コンパイラによって作成される匿名型のコードとオーバーライドされたメソッドの機能の詳細については、「匿名型の定義」を参照してください。

関連項目