次の方法で共有


共用体の型 (C# リファレンス)

共用体の型は、複数のケース型のいずれかを指定できる値を表します。 共用体は、各ケースの種類からの暗黙的な変換、完全なパターン マッチング、および強化された null 許容追跡を提供します。 共用体の型を宣言するには、 union キーワードを使用します。

public union Pet(Cat, Dog, Bird);

この宣言では、CatDogBirdの 3 つのケースの種類を持つPet共用体が作成されます。 Pet変数には、任意のケース型の値を割り当てることができます。 コンパイラは、 switch 式がすべてのケースの種類をカバーすることを保証します。

C# 言語リファレンスには、C# 言語の最新リリース バージョンが記載されています。 また、今後の言語リリースのパブリック プレビューの機能に関する初期ドキュメントも含まれています。

このドキュメントでは、言語の最後の 3 つのバージョンまたは現在のパブリック プレビューで最初に導入された機能を特定します。

ヒント

C# で機能が初めて導入された時期を確認するには、 C# 言語バージョン履歴に関する記事を参照してください。

値が固定型のセットのいずれかである必要があり、すべての可能性が処理されるようにコンパイラに強制する必要がある場合は、共用体を宣言します。 一般的なシナリオは、次のとおりです。

  • 結果またはエラーが返されます。メソッドは成功値またはエラー値を返し、呼び出し元は両方を処理する必要があります。 union Result(Success, Error)のような共用体は、結果のセットを明示的にします。
  • メッセージまたはコマンドのディスパッチ: システムは、閉じたメッセージの種類のセットを処理します。 共用体を使用すると、新しいメッセージの種類では、まだ処理されないすべての switch でコンパイル時の警告が生成されます。
  • マーカー インターフェイスまたは抽象基底クラスの置き換え: パターン マッチングの型をグループ化するためだけにインターフェイスまたは抽象クラスを使用する場合、共用体は継承や共有メンバーを必要とせずに完全なチェックを提供します。

共用体は、重要な点で他の型宣言とは異なります。

  • classstructとは異なり、共用体では新しいデータ メンバーは定義されません。 代わりに、既存の型を閉じた代替セットに構成します。
  • interfaceとは異なり、共用体は閉じられます。宣言でケース型の完全なリストを定義し、コンパイラはそのリストを使用して完全なチェックを行います。
  • recordとは異なり、共用体は等価性、複製、または分解動作を追加しません。 共用体では、"どのフィールドがありますか" ではなく、"どのケースですか" に焦点を当てています。

Important

.NET 11 Preview 2 では、ランタイムに UnionAttribute インターフェイスと IUnion インターフェイスは含まれません。 共用体型を使用するには、それらを自分で宣言する必要があります。 必要な宣言を確認するには、 Union の実装を参照してください。

共用体宣言

共用体宣言では、名前とケースの種類の一覧を指定します。

public union Pet(Cat, Dog, Bird);

ケース型 には、クラス、構造体、インターフェイス、型パラメーター、null 許容型、その他の共用体など、 objectに変換する任意の型を指定できます。 次の例は、さまざまなケースの種類の可能性を示しています。

public record class Cat(string Name);
public record class Dog(string Name);
public record class Bird(string Name);
public record class None;
public record class Some<T>(T Value);
public union Option<T>(None, Some<T>);
public union IntOrString(int, string);

ケース型が値型 ( int など) の場合、共用体の Value プロパティに格納されるときに値がボックス化されます。 共用体は、その内容を単一の object? 参照として格納します。

共用体宣言には、構造体と同様に、いくつかの制限に従って、追加のメンバーを含む本文を含めることができます。 共用体宣言には、インスタンス フィールド、自動プロパティ、またはフィールドに似たイベントを含めることはできません。 コンパイラではこれらのコンストラクターが共用体作成メンバーとして生成されるため、パブリック コンストラクターを 1 つのパラメーターで宣言することもできません。

public union OneOrMore<T>(T, IEnumerable<T>)
{
    public IEnumerable<T> AsEnumerable() => Value switch
    {
        T single => [single],
        IEnumerable<T> multiple => multiple,
        _ => []
    };
}

共用体の変換

各ケース型から共用体型への暗黙的な 共用体変換 が存在します。 コンストラクターを明示的に呼び出す必要はありません。

static void BasicConversion()
{
    Pet pet = new Dog("Rex");
    Console.WriteLine(pet.Value); // output: Dog { Name = Rex }

    Pet pet2 = new Cat("Whiskers");
    Console.WriteLine(pet2.Value); // output: Cat { Name = Whiskers }
}

共用体変換は、対応する生成されたコンストラクターを呼び出すことによって機能します。 同じ型に対してユーザー定義の暗黙的な変換演算子が存在する場合、ユーザー定義演算子は共用体変換よりも優先されます。 変換の優先順位の詳細については、 言語の仕様を参照してください。

null 許容共用体構造体 (T?) への共用体変換は、 T が共用体型の場合にも機能します。

static void NullableUnionExample()
{
    Pet? maybePet = new Dog("Buddy");
    Pet? noPet = null;

    Console.WriteLine(Describe(maybePet)); // output: Dog: Buddy
    Console.WriteLine(Describe(noPet));    // output: no pet

    static string Describe(Pet? pet) => pet switch
    {
        Dog d => d.Name,
        Cat c => c.Name,
        Bird b => b.Name,
        null => "no pet",
    };
}

和集合の照合

共用体の型でパターンマッチングを行う場合、パターンは共用体の値自体ではなく、共用体の Value プロパティに適用されます。 この "ラップ解除" 動作は、共用体がパターン マッチングに対して透過的であることを意味します。

static void PatternMatching()
{
    Pet pet = new Dog("Rex");

    var name = pet switch
    {
        Dog d => d.Name,
        Cat c => c.Name,
        Bird b => b.Name,
    };
    Console.WriteLine(name); // output: Rex
}

この規則の例外は、2 つのパターンです。 var パターンと破棄 _ パターンは、その Value プロパティではなく、共用体値自体に適用されます。 varを使用して、GetPet()Pet? (Nullable<Pet>) を返すときに共用体の値をキャプチャします。

if (GetPet() is var pet) { /* pet is the Pet? value returned from GetPet */ }

論理パターンでは、各分岐はラップ解除ルールに個別に従います。 次のパターンは、 Pet? が null ではなく その Value が null ではないことをテストします。

GetPet() switch
{
    var pet and not null => ..., // 'var pet' captures the Pet?; 'not null' checks Value
}

パターンはValueに適用されるため、Petは共用体自体ではなく共用体の内容に対してテストされるため、通常、pet is Petのようなパターンは一致しません。

Null 照合

構造体共用体の場合、 null パターンは、 Value が null であるかどうかをチェックします。

static void NullHandling()
{
    Pet pet = default;
    Console.WriteLine(pet.Value is null); // output: True

    var description = pet switch
    {
        Dog d => d.Name,
        Cat c => c.Name,
        Bird b => b.Name,
        null => "no pet",
    };
    Console.WriteLine(description); // output: no pet
}

クラス ベースの共用体の場合、共用体参照自体が null であるか、そのValueプロパティが null の場合、nullは成功します。

Result<string>? result = null;
if (result is null) { /* true — the reference is null */ }

Result<string> empty = new Result<string>((string?)null);
if (empty is null) { /* true — Value is null */ }

null 許容共用体構造体型 (Pet?) の場合、 null は、null 許容ラッパーに値がない場合、または基になる共用体の Value が null の場合に成功します。

和集合の網羅性

switch式は、共用体のすべてのケース型を処理する場合に網羅的です。 コンパイラは、ケースの種類が処理されない場合にのみ警告します。 破棄パターン (_) または var パターンを含める必要はありません。

static void PatternMatching()
{
    Pet pet = new Dog("Rex");

    var name = pet switch
    {
        Dog d => d.Name,
        Cat c => c.Name,
        Bird b => b.Name,
    };
    Console.WriteLine(name); // output: Rex
}

共用体の Value プロパティの null 状態が "null の可能性があります" の場合は、警告を回避するために null も処理する必要があります。

static void NullHandling()
{
    Pet pet = default;
    Console.WriteLine(pet.Value is null); // output: True

    var description = pet switch
    {
        Dog d => d.Name,
        Cat c => c.Name,
        Bird b => b.Name,
        null => "no pet",
    };
    Console.WriteLine(description); // output: no pet
}

NULL 値の許容

コンパイラは、次の規則を使用して、共用体の Value プロパティの null 状態を追跡します。

  • ケース型から (コンストラクターまたは共用体の変換を使用して) 共用体値を作成すると、 Value は受信値の null 状態を取得します。
  • ボックス化されていないアクセス パターンの HasValue または TryGetValue(...) メンバーが共用体の内容に対してクエリを実行すると、 Value の null 状態は true ブランチで "not null" になります。

カスタム共用体の型

コンパイラは、 union 宣言を struct 宣言に変換します。 構造体は [System.Runtime.CompilerServices.Union] 属性でマークされ、 IUnion インターフェイスを実装します。 これには、パブリック コンストラクターと、各ケース型の暗黙的な変換と、 Value プロパティが含まれます。 その生成されたフォームは意見が出されています。 これは常に構造体であり、常に値型のケースをボックス化し、常にコンテンツを object?として格納します。

クラス ベースの共用体、カスタム ストレージ戦略、相互運用サポート、既存の型を調整する場合など、さまざまな動作が必要な場合は、共用体の型を手動で作成できます。

基本的な共用体パターンに従う場合、[Union]属性を持つクラスまたは構造体は共用体型です。 基本的な共用体パターンには、次のものが必要です。

  • 型の [Union] 属性。
  • 1 つ以上のパブリック コンストラクター。それぞれが単一の値渡しまたは in パラメーターを持ちます。 各コンストラクターのパラメーター型は 、ケース型を定義します。
  • get アクセサーobject? (またはobject) 型のパブリック Value プロパティ。

すべての共用体メンバーはパブリックでなければなりません。 コンパイラはこれらのメンバーを使用して、共用体変換、パターン マッチング、および網羅性チェックを実装します。 また、 非ボックス化アクセス パターンを 実装したり、 クラスベースの共用体型を作成したりすることもできます。

コンパイラは、カスタム共用体の型が次の動作規則を満たすことを前提としています。

  • Soundness: Value は常に null またはケースの種類の 1 つの値を返します。別の型の値は返されません。 構造体共用体の場合、defaultnullValueを生成します。
  • 安定性: ケース型から共用体値を作成した場合、Valueはそのケースの種類と一致します (または、入力がnullされた場合はnull)。
  • 作成の等価性: 値が 2 つの異なるケース型に暗黙的に変換できる場合、両方の作成メンバーが同じ監視可能な動作を生成します。
  • アクセス パターンの整合性: HasValue メンバーと TryGetValue メンバー (存在する場合) は、 Value を直接チェックするのと同じように動作します。

次の例は、カスタム共用体の型を示しています。

[System.Runtime.CompilerServices.Union]
public struct Shape : System.Runtime.CompilerServices.IUnion
{
    private readonly object? _value;

    public Shape(Circle value) { _value = value; }
    public Shape(Rectangle value) { _value = value; }

    public object? Value => _value;
}

public record class Circle(double Radius);
public record class Rectangle(double Width, double Height);
static void ManualUnionExample()
{
    Shape shape = new Shape(new Circle(5.0));

    var area = shape switch
    {
        Circle c => Math.PI * c.Radius * c.Radius,
        Rectangle r => r.Width * r.Height,
    };
    Console.WriteLine($"{area:F2}"); // output: 78.54
}

非ボックス化アクセス パターン

カスタム共用体の種類では、必要に応じて 、非ボックス化アクセス パターンを 実装して、パターン マッチング中にボックス化を行わずに値型のケースに対して厳密に型指定されたアクセスを有効にすることができます。 このパターンでは、次のものが必要です。

  • Valuenullされていない場合にtrueを返すbool型のHasValueプロパティ。
  • boolを返し、out パラメーターを使用して値を提供する、各ケース型のTryGetValue メソッド。
[System.Runtime.CompilerServices.Union]
public struct IntOrBool : System.Runtime.CompilerServices.IUnion
{
    private readonly int _intValue;
    private readonly bool _boolValue;
    private readonly byte _tag; // 0 = none, 1 = int, 2 = bool

    public IntOrBool(int? value)
    {
        if (value.HasValue)
        {
            _intValue = value.Value;
            _tag = 1;
        }
    }

    public IntOrBool(bool? value)
    {
        if (value.HasValue)
        {
            _boolValue = value.Value;
            _tag = 2;
        }
    }

    public object? Value => _tag switch
    {
        1 => _intValue,
        2 => _boolValue,
        _ => null
    };

    public bool HasValue => _tag != 0;

    public bool TryGetValue(out int value)
    {
        value = _intValue;
        return _tag == 1;
    }

    public bool TryGetValue(out bool value)
    {
        value = _boolValue;
        return _tag == 2;
    }
}
static void NonBoxingExample()
{
    IntOrBool val = new IntOrBool((int?)42);

    var description = val switch
    {
        int i => $"int: {i}",
        bool b => $"bool: {b}",
    };
    Console.WriteLine(description); // output: int: 42
}

コンパイラは、パターン マッチングを実装するときに、Value プロパティよりもTryGetValueを優先します。これにより、値型のボックス化が回避されます。

クラス ベースの共用体型

クラスは共用体型にすることもできます。 この種類の共用体は、参照セマンティクスまたは継承が必要な場合に便利です。

[System.Runtime.CompilerServices.Union]
public class Result<T> : System.Runtime.CompilerServices.IUnion
{
    private readonly object? _value;

    public Result(T? value) { _value = value; }
    public Result(Exception? value) { _value = value; }

    public object? Value => _value;
}
static void ClassUnionExample()
{
    Result<string> ok = new Result<string>("success");
    Result<string> err = new Result<string>(new InvalidOperationException("failed"));

    Console.WriteLine(Describe(ok));  // output: OK: success
    Console.WriteLine(Describe(err)); // output: Error: failed

    static string Describe(Result<string> result) => result switch
    {
        string s => $"OK: {s}",
        Exception e => $"Error: {e.Message}",
        null => "null",
    };
}

クラス ベースの共用体の場合、 null パターンは null 参照と null Valueの両方に一致します。

Union の実装

次の属性とインターフェイスは、コンパイル時と実行時に共用体の型をサポートします。

namespace System.Runtime.CompilerServices
{
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false)]
    public sealed class UnionAttribute : Attribute;

    public interface IUnion
    {
        object? Value { get; }
    }
}

コンパイラによって生成された共用体宣言は、 IUnionを実装します。 IUnionを使用して、実行時に任意の共用体値を確認できます。

if (value is IUnion { Value: null }) { /* the union's value is null */ }

union型を宣言すると、コンパイラはIUnionを実装する構造体を生成します。 たとえば、 Pet 宣言 (public union Pet(Cat, Dog, Bird);) は次のようになります。

[Union] public struct Pet : IUnion
{
    public Pet(Cat value) => Value = value;
    public Pet(Dog value) => Value = value;
    public Pet(Bird value) => Value = value;
    public object? Value { get; }
}

Important

.NET 11 Preview 2 では、これらの型はランタイムに含まれません。 共用体の型を使用するには、それらをプロジェクトで宣言する必要があります。 今後の .NET プレビューに含まれる予定です。

C# 言語仕様

詳細については、 共用体 機能の仕様を参照してください。

こちらも参照ください