**C# 15 のユニオン型(Union Types)**

C# 15 では *union types*(別名「discriminated unions」や「tagged unions」)が導入され、変数が複数の異なる名前付き代替値のいずれかを保持できるようになりつつ、型安全性も維持できます。

---

### 1. ユニオン型の定義

```csharp
public union Result<TSuccess, TError>
{
    // 成功の場合
    public readonly record struct Ok(TSuccess Value);

    // エラーの場合
    public readonly record struct Err(TError Reason);
}
```

- `Result<int, string>` は `Ok`(整数)または `Err`(文字列)のいずれかを保持できます。
- 各代替値は *record struct* であるため、イミュータブルで軽量です。

---

### 2. ユニオンの使用

```csharp
Result<int, string> GetNumber(bool success)
{
    return success ? new Result<int, string>.Ok(42) : new Result<int, string>.Err("failed");
}

var result = GetNumber(true);

result.Match(
    ok => Console.WriteLine($"Success: {ok.Value}"),
    err => Console.Error.WriteLine($"Error: {err.Reason}")
);
```

- `Match` はパターンマッチングのヘルパーで、各代替値に対してデリゲートを受け取ります。
- C# の `switch` 式でも同様に扱えます:

```csharp
var message = result switch
{
    Result<int, string>.Ok(ok) => $"Success: {ok.Value}",
    Result<int, string>.Err(err) => $"Error: {err.Reason}"
};
```

---

### 3. メリット

| 機能 | 重要性 |
|------|--------|
| **型安全** | コンパイル時にすべての代替値が処理されることを保証します。 |
| **イミュータブル** | record struct により不注意な変更を防止します。 |
| **パターンマッチング** | 各ケースを簡潔かつ表現力豊かに扱えます。 |
| **外部ライブラリ不要** | 言語内蔵であり、`OneOf` などのサードパーティパッケージは必要ありません。 |

---

### 4. よく使われるパターン

- **Result / Either** – 成功 vs エラー(`Ok/Err`、`Right/Left`)。  
- **Option / Maybe** – 値あるいはなし(`Some/Nothing`)。  
- **状態機械** – 状態を個別の代替値として扱う。

```csharp
public union State
{
    public readonly record struct Idle;
    public readonly record struct Running(int Count);
    public readonly record struct Stopped(string Reason);
}
```

---

### 5. 相互運用性

ユニオンを、別々の型を期待する API に渡す際はパターンマッチングで内部値を抽出できます:

```csharp
if (result is Result<int, string>.Ok(ok))
{
    ProcessValue(ok.Value);   // ok は Result<int,string>.Ok 型です
}
```

---

### 6. 留意点

- **シリアライゼーション** – カスタム処理が必要になる場合があります。ユニオンは自動的にシリアライズ可能ではありません。  
- **パフォーマンス** – 各代替値は独立した struct であるため、高スループット環境では過度な箱詰めを避けるべきです。  
- **ツールサポート** – IDE はユニオン型を理解しますが、古いツールでは対応していない可能性があります。

---

### 7. クイックリファレンス

```csharp
// 宣言
public union MyUnion { public readonly record struct A(int X); public readonly record struct B(string Y); }

// インスタンス生成
var a = new MyUnion.A(10);
var b = new MyUnion.B("hello");

// パターンマッチング
myValue.Match(
    a => HandleA(a.X),
    b => HandleB(b.Y)
);

// Switch 式
string result = myValue switch
{
    MyUnion.A(a) => $"A: {a.X}",
    MyUnion.B(b) => $"B: {b.Y}"
};
```

---

**結論**  
C# 15 のユニオン型は、外部ライブラリのボイラープレートを排除しつつ、エラー処理や状態表現などで「複数形態の値」をクリーンにかつ型安全に扱うための強力な手段です。

2026/04/05 11:53

**C# 15 のユニオン型(Union Types)** C# 15 では *union types*(別名「discriminated unions」や「tagged unions」)が導入され、変数が複数の異なる名前付き代替値のいずれかを保持できるようになりつつ、型安全性も維持できます。 --- ### 1. ユニオン型の定義 ```csharp public union Result<TSuccess, TError> { // 成功の場合 public readonly record struct Ok(TSuccess Value); // エラーの場合 public readonly record struct Err(TError Reason); } ``` - `Result<int, string>` は `Ok`(整数)または `Err`(文字列)のいずれかを保持できます。 - 各代替値は *record struct* であるため、イミュータブルで軽量です。 --- ### 2. ユニオンの使用 ```csharp Result<int, string> GetNumber(bool success) { return success ? new Result<int, string>.Ok(42) : new Result<int, string>.Err("failed"); } var result = GetNumber(true); result.Match( ok => Console.WriteLine($"Success: {ok.Value}"), err => Console.Error.WriteLine($"Error: {err.Reason}") ); ``` - `Match` はパターンマッチングのヘルパーで、各代替値に対してデリゲートを受け取ります。 - C# の `switch` 式でも同様に扱えます: ```csharp var message = result switch { Result<int, string>.Ok(ok) => $"Success: {ok.Value}", Result<int, string>.Err(err) => $"Error: {err.Reason}" }; ``` --- ### 3. メリット | 機能 | 重要性 | |------|--------| | **型安全** | コンパイル時にすべての代替値が処理されることを保証します。 | | **イミュータブル** | record struct により不注意な変更を防止します。 | | **パターンマッチング** | 各ケースを簡潔かつ表現力豊かに扱えます。 | | **外部ライブラリ不要** | 言語内蔵であり、`OneOf` などのサードパーティパッケージは必要ありません。 | --- ### 4. よく使われるパターン - **Result / Either** – 成功 vs エラー(`Ok/Err`、`Right/Left`)。 - **Option / Maybe** – 値あるいはなし(`Some/Nothing`)。 - **状態機械** – 状態を個別の代替値として扱う。 ```csharp public union State { public readonly record struct Idle; public readonly record struct Running(int Count); public readonly record struct Stopped(string Reason); } ``` --- ### 5. 相互運用性 ユニオンを、別々の型を期待する API に渡す際はパターンマッチングで内部値を抽出できます: ```csharp if (result is Result<int, string>.Ok(ok)) { ProcessValue(ok.Value); // ok は Result<int,string>.Ok 型です } ``` --- ### 6. 留意点 - **シリアライゼーション** – カスタム処理が必要になる場合があります。ユニオンは自動的にシリアライズ可能ではありません。 - **パフォーマンス** – 各代替値は独立した struct であるため、高スループット環境では過度な箱詰めを避けるべきです。 - **ツールサポート** – IDE はユニオン型を理解しますが、古いツールでは対応していない可能性があります。 --- ### 7. クイックリファレンス ```csharp // 宣言 public union MyUnion { public readonly record struct A(int X); public readonly record struct B(string Y); } // インスタンス生成 var a = new MyUnion.A(10); var b = new MyUnion.B("hello"); // パターンマッチング myValue.Match( a => HandleA(a.X), b => HandleB(b.Y) ); // Switch 式 string result = myValue switch { MyUnion.A(a) => $"A: {a.X}", MyUnion.B(b) => $"B: {b.Y}" }; ``` --- **結論** C# 15 のユニオン型は、外部ライブラリのボイラープレートを排除しつつ、エラー処理や状態表現などで「複数形態の値」をクリーンにかつ型安全に扱うための強力な手段です。

RSS: https://news.ycombinator.com/rss

要約

Japanese Translation:

**C# 15 は

union
キーワードを使用して union 型を導入し、最初に .NET 11 Preview 2 で利用可能になりました。ユニオンは関連のないケース型の閉じた集合を宣言します;コンパイラは除外 (
_
) やデフォルトブランチを必要とせずに網羅的なパターンマッチングを強制します。例えば:

public union Pet(Cat, Dog, Bird);

この型は各ケースクラスから暗黙の変換が可能で、

Value
プロパティ経由で値にアクセスできます。非 null のユニオンインスタンスに対する switch 式は網羅的です;新しいケースを追加すると欠落しているアームについてコンパイル時警告が発生します。ユニオンのデフォルト構造体値には null
Value
が設定されるため、switch で
null => …
アームを含めてその状態を処理できます。
ユニオンは追加メンバーも持つことができ、例として
public union OneOrMore<T>(T, IEnumerable<T>) { public IEnumerable<T> AsEnumerable() => … }
などがあります。既存のライブラリは
[System.Runtime.CompilerServices.Union]
を付与し、必要なコンストラクタと
Value
プロパティを提供することでユニオンとして扱うことができます。
パフォーマンス向上のために、ユニオンは
HasValue
TryGetValue
、または値型のボクシングを避ける非ボクシングパターンを公開する場合があります。「閉じた階層」(sealed クラス)や「閉じた列挙」などの関連提案も、閉じた集合に対して同様の網羅的マッチングを実現しようとします。
プレビューでユニオン型を試すには、.NET 11 Preview SDK をインストールし、ターゲットを
net11.0
に設定し、プロジェクトファイルに
<LangVersion>preview</LangVersion>
を追加します。また、早期プレビューを使用する場合は
UnionAttribute
IUnion
を宣言してください。IDE サポートは今後の Visual Studio Insiders ビルドで提供される予定です;ユーザーフィードバックが将来のリリース形成に役立ちます。**

本文

C# / .NET プリンシパル コンテンツ デベロッパー

C# で「ユニオン型」が頻繁に要望されており、ついに実装されました。 .NET 11 Preview 2 以降、C# 15 は

union
キーワードを導入しています。このキーワードは、コンパイラが厳密に検証する「閉じた型集合」のうちの 1 つだけ を保持できる値であることを宣言します。F# の discriminated unions や他言語の類似機能をご存知なら、すぐに馴染めるでしょう。ただし C# ユニオンは C# 本来の体験を意図して設計されており、既存の型と組み合わせて使い、パターンマッチングとの親和性も高く、言語全体とシームレスに統合されます。


ユニオン型とは?

C# 15 以前では、メソッドが複数種類の値を返す必要がある場合、次のような選択肢しかありませんでした:

選択肢説明
object
型に制約がないため任意の型が入れられる。呼び出し側は予期せぬ値に対して防御的なロジックを書く必要がある。
マーカーインタフェース/抽象基底クラス制限された型集合を示すことはできるが、閉じた(完全)な集合とは言えない。誰でも実装・派生できるためコンパイラは完結性を保証できない。また共通の祖先が必要であり、
string
Exception
のように相関しない型同士のユニオンは表現できない。

ユニオン型 はこれらの問題を解決します。ユニオンは 閉じた 型集合(ケースタイプ)を宣言します。ケースタイプ間で親子関係は必要なく、他の型が追加されることもありません。コンパイラはスイッチ式が exhaustive になるよう保証し、デフォルト (

_
) や
default
ブランチなしにすべてのケースを網羅できます。さらに、従来の階層構造では表現できない設計(任意の既存型の組み合わせ)をコンパイラが検証した契約として実装します。


シンプルな宣言例

public record class Cat(string Name);
public record class Dog(string Name);
public record class Bird(string Name);

public union Pet(Cat, Dog, Bird);

上記 1 行で

Pet
は新しい型として宣言され、変数は
Cat
,
Dog
,
Bird
のいずれかを保持できます。コンパイラは各ケースタイプからの暗黙的な変換を提供します。

Pet pet  = new Dog("Rex");
Console.WriteLine(pet.Value);      // Dog { Name = Rex }

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

ケースタイプに含まれない型を代入するとコンパイルエラーになります。

ユニオン型のインスタンスが null でない とき、コンパイラはケース集合全体を把握しているため、すべてを網羅したスイッチ式は exhaustive です。

string name = pet switch
{
    Dog d => d.Name,
    Cat c => c.Name,
    Bird b => b.Name,
};

いずれかのケースが nullable(例:

int?
,
Bird?
)の場合、すべてのスイッチ式は null ブランチを必要とします。ケースタイプを 4 種類に増やした場合、未網羅のスイッチにはコンパイル警告が発生します。これは「ビルド時に欠落ケースを検出」できるというユニオン型の主要な価値です。

パターンは

Value
プロパティ に対して適用され、ユニオン構造体自体ではありません。この「アンラップ」は自動で行われます。例外として
var
_
はユニオン全体に対して適用でき、全体を取得または無視できます。

null パターンは

Value
が null かどうかを判定します。デフォルト値のユニオン構造体は
Value
が null の状態です。

Pet pet = default;

var description = pet switch
{
    Dog d => d.Name,
    Cat c => c.Name,
    Bird b => b.Name,
    null => "no pet",
};
// description は "no pet"

実務で使えるユニオン型のシナリオ

OneOrMore<T>
― 単一値またはコレクション

API が「単一アイテム」か「複数アイテム」のいずれかを受け取る場合、ユニオンに本体(body)を持たせてヘルパーメンバーを追加できます。

OneOrMore<T>
の宣言は
AsEnumerable()
メソッドを直接ユニオンの本体で定義しています。

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

AsEnumerable()
Value
が null のケースも処理する必要があります。呼び出し側は便利な形式を渡せば、
AsEnumerable()
がそれを正規化します。

OneOrMore<string> tags  = "dotnet";
OneOrMore<string> moreTags = new[] { "csharp", "unions", "preview" };

foreach (var tag in tags.AsEnumerable())
    Console.Write($"[{tag}] ");   // [dotnet]

foreach (var tag in moreTags.AsEnumerable())
    Console.Write($"[{tag}] ");  // [csharp] [unions] [preview]

既存ライブラリ向けのカスタムユニオン

ユニオン宣言は構文上の短縮形です。コンパイラは各ケースタイプ用のコンストラクタと

object?
型の
Value
プロパティを持つ構造体を生成します。コンストラクタにより、任意のケースタイプからユニオン型への暗黙的変換が可能です。ユニオンインスタンスは常に内部で単一の
object?
参照として保持し、値型はボックス化されます。

既存のコミュニティライブラリで「ユニオン風」型を使用している場合も、構文を変えずに恩恵を受けられます。

[System.Runtime.CompilerServices.Union]
属性が付与されたクラスまたは構造体は、以下の基本パターン(1 つ以上の public single‑parameter コンストラクタと public
Value
プロパティ)に従っていればユニオン型として認識されます。

性能重視で値型をケースタイプに含める場合、ライブラリは非ボックス化アクセスパターン(

HasValue
プロパティや
TryGetValue
メソッド)の実装も可能です。これによりコンパイラはパターンマッチングをボックス化せずに行えます。

カスタムユニオン型の作成と非ボックス化アクセスパターンについては、ユニオン型言語リファレンスをご覧ください。


関連提案

ユニオン型は「閉じた型集合」を持つタイプを提供します。型階層や列挙体に対しても同様の機能を実現する提案が 2 つあります:

  • Closed hierarchies
    closed
    修飾子をクラスに付与すると、定義アセンブリ外で派生クラスを宣言できなくなります。
  • Closed enums ― 列挙体を閉じることで、宣言されたメンバー以外の値は作れません。

これら 3 つの機能により、C# は包括的な exhaustive マッチングストーリーを構築します:

機能exhaustive なマッチ対象
ユニオン型閉じた型集合
Closed hierarchiesシールドクラス階層
Closed enums固定列挙値

ユニオン型は現在プレビューで利用可能です。評価の際には、上記ロードマップを考慮してください。これら提案はアクティブですが、まだリリースに確約されているわけではありません。


使ってみる

ユニオン型は .NET 11 Preview 2 以降で利用可能です。

  1. .NET 11 Preview SDK をインストールします。
  2. net11.0
    をターゲットにしたプロジェクトを作成または更新します。
  3. プロジェクトファイルに
    <LangVersion>preview</LangVersion>
    を設定します。

Visual Studio では次の Visual Studio Insiders ビルド(最新の C# DevKit Insiders に含まれます)で IDE サポートが利用できます。

初期プレビュー:ランタイム型を自分で宣言

.NET 11 Preview 2 では

UnionAttribute
IUnion
はまだランタイムに組み込まれていません。プロジェクト内で宣言しておけば、後続のプレビューでこれらが含まれるようになります。

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

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

これでユニオン型を宣言・使用できます。

public record class Cat(string Name);
public record class Dog(string Name);

public union Pet(Cat, Dog);

Pet pet = new Cat("Whiskers");
Console.WriteLine(pet switch
{
    Cat c => $"Cat: {c.Name}",
    Dog d => $"Dog: {d.Name}",
});

完全仕様に含まれる一部機能(ユニオンメンバープロバイダーなど)はまだ実装されていません。今後のプレビューで追加予定です。


フィードバックを共有

ユニオン型は現在プレビュー段階です。直接的に最終設計に影響しますので、ぜひプロジェクトで試し、エッジケースや課題点を教えてください。


著者

C# / .NET プリンシパル コンテンツ デベロッパー
Bill Wagner は https://docs.microsoft.com/dotnet/csharp のドキュメントを書いています。彼のチームは docs.microsoft.com 上の .NET コンテンツ全般を担当しています。また、C# 標準化委員会にも所属しています。

同じ日のほかのニュース

一覧に戻る →

2026/04/09 0:40

私、macOS XをNintendo Wiiにポート(移植)いたしました。

## Japanese Translation: --- ## 改良された要約 Mac OS X 10.0(Cheetah)は、Nintendo Wii 上でネイティブに動作するようにポートされ、コンソールをキーボード/マウス入力と GUI サポート付きの完全機能型デスクトップへ変貌させました。プロジェクトのコアは、*ppcskel* をベースに最初から書き直されたカスタムブートローダーです。このブートローダーは、Wii の PowerPC 750CL CPU を起動し、メモリレイアウトを設定し、最小限のデバイスツリー(root → cpus → PowerPC,750; memory)を作成します。SD カードから XNU カーネルをロードし、実行中にカーネルバイナリをパッチ(MEM1/MEM2 用の BAT 設定と USB Gecko へのコンソール出力)し、制御を XNU に渡します。 ブートローダーが提供する主要ドライバーは次の通りです: - **SD‑カードドライバー**:Starlet MINI IPC コマンド(IPC_SDMMC_SIZE, READ, WRITE)を介して IOBlockStorageDevice を実装し、XNU が SD カードからルートファイルシステムをマウントできるようにします。 - **フレームバッファドライバー**:0x01700000 に RGB フレームバッファ(640×480 @ 16 bpp)を提供し、Wii のアナログテレビ出力用に YUV へ変換して Mac OS X GUI を実現します。 - **USB サポート**:PCI デバイスのニブ(NintendoWiiHollywoodPCIDevice)を作成し、AppleUSBOHCI をパッチして受け入れさせ、OHCI ドライバーからバイトスワップ処理を除去することでリバースレトルエンディアンハードウェアに対応し、USB キーボード/マウス機能をフル実装します。 ブートローダーは Apple Partition Map を解析し、起動可能なパーティションを一覧表示し、/chosen/memory‑map ノード経由でカーネル拡張を直接メモリにロードできるようにするため、改変されていない Mac OS X インストーラーパーティションからのインストールも可能です。必要なカーネル変更は最小限(BAT 設定、“hollywood” I/O ベース取得、フレームバッファキャッシュ整合性修正)で済み、その他すべてのドライバーはブートローダーが提供します。 この成果は、歴史的にサポートされていなかったプラットフォーム――Nintendo Wii――でも Mac OS X Cheetah をエンドツーエンドで動作させることを示し、ホビイストに低コストのレトロコンソールとして機能するデスクトップコンピュータを提供します。

2026/04/09 4:23

**ソフトウェア開発者のためのUSB:ユーザースペース USB ドライバー作成入門**

## Japanese Translation: ``` USB デバイスの操作は、libusb を使用してユーザー空間だけで完全に処理できるため、カーネルレベルのドライバ開発は不要です。 例として、Fastboot モード(VID 18d1 / PID 4ee0)にある Android フォンを挙げます。接続すると `lsusb` は「Google Inc. Nexus/Pixel Device (fastboot)」と表示し、カーネルドライバは付いていません。また、ベンダー固有クラスインターフェースが 2 つのバルクエンドポイントを公開します:コマンド送信用 OUT 0x02 とレスポンス受信用 IN 0x81。 libusb のホットプラグコールバックはこのデバイスの到着を検出し、Fastboot コマンドを自動的に発行できます。典型的な手順は次のとおりです: 1. `libusb_control_transfer` を使用して GET_STATUS リクエストを送信します。2 バイトの応答はデバイスがセルフパワーであり、リモートウェイクアップをサポートしないことを示します。 2. GET_DESCRIPTOR リクエストを送信して完全なデバイスディスクリプタ(ベンダー/プロダクト ID、USB バージョン等)を取得します。 3. バルク OUT 0x02 を介して Fastboot コマンドを発行します(例:「getvar:version」を 64 バイトにパディング)。 デバイスは IN 0x81 で 4 文字のステータス(「OKAY」または「FAIL」)と任意のペイロードを返します。 同じユーザー空間アプローチは、バルク転送に依存する他の USB プロトコルにも適用できます。主な作業はカーネルコードを書く代わりにプロトコルロジックを実装することです。これにより OEM 向けドライバ開発が簡素化され、ブートローダーのテストが迅速化し、カーネルモジュールなしでカスタム USB デバイスの高速プロトタイピングやデバッグが可能になり、組込み開発者と広範な USB エコシステムに恩恵をもたらします。 ```

2026/04/08 17:53

**コードを読む前に実行しておくべき一般的な Git コマンド** - `git fetch --all` *リモートの全ブランチとタグを取得します。* - `git status` *現在のブランチと未コミットの変更点を確認します。* - `git checkout <branch>` *対象となる機能やバグ修正用ブランチに切り替えます。* - `git pull --rebase` *ローカルブランチを最新の upstream コミットで更新します。* - `git log --oneline --graph --decorate -5` *簡潔なコミット履歴を表示し、文脈を把握します。* - `git diff origin/<branch>..HEAD` *まだプッシュしていない変更点を確認します。* - `git rev-parse HEAD` *現在のコミットハッシュを取得(参照に便利)。* - `git tag --list` *利用可能なタグ一覧を表示し、バージョン管理に役立てます。* - `git show <commit>` *特定のコミットの詳細と差分を調べます。* これらのコマンドで、コードを掘り下げる前にリポジトリの状態を素早く把握できます。

## 日本語訳: 以下の文章を日本語に翻訳してください。 ### 修正版要約 この記事は、ソースファイルを検査する前にコードベースの簡易監査が隠れた健康リスクを明らかにできる方法を示しています。これは5つの簡潔な Git コマンドを実行することで達成されます。 1. `git log --format=format: --name-only --since="1 year ago" | sort | uniq -c | sort -nr | head -20` 過去 1 年間で最も変更頻度が高い上位 20 ファイルを一覧表示し、潜在的な「ドラッグ」スポット(高い変更率)をフラグ付けします。 2. `git shortlog -sn --no-merges` コミット数で貢献者をランク付けします。単一人物が 70 % 超を占める場合はバスファクターが低く、過去 6 ヶ月にその貢献者がいない場合は危機的状況を示唆します。 3. `git log -i -E --grep="fix|bug|broken" --name-only --format='' | sort | uniq -c | sort -nr | head -20` バグ関連コミットが最も多いファイルを特定し、変更率データと照合して最高リスクコードをピンポイントします。 4. `git log --format='%ad' --date=format:'%Y-%m' | sort | uniq -c` 月ごとのコミット数を表示し、活動の加速または減退(例:半月間のドロップ)が重要人物の離脱を示す可能性があります。 5. `git log --oneline --since="1 year ago" | grep -iE 'revert|hotfix|emergency|rollback'` リバートとホットフィックスの数をカウントします。頻繁なリバートはデプロイ/テストが不安定であることを示し、ゼロの場合は安定性またはコミットメッセージ不足を意味する可能性があります。 これらの指標(変更ホットスポット、バスファクター問題、バグクラスタ、プロジェクトモーメンタム、火災対策頻度)は、コード複雑度測定だけよりも欠陥予測精度が高いと示されています(Microsoft Research 2005)。記事はスクワッシュマージワークフローが著者データを歪めることを警告しています。最初の監査に1時間を費やした後、筆者は特定されたリスクスポットに対して週単位で詳細調査を計画しています。関連研究としてはエンジニアリングチーム速度、Vim 使用、レガシー Rails 監査、Rails `default_scope` が引用されています。この手法は開発者に迅速なコミット履歴ベースの診断を提供し、高リスクファイルへの詳細コードレビューを集中させることでバグ削減、チームレジリエンス、およびリリース信頼性の向上を実現します。

**C# 15 のユニオン型(Union Types)** C# 15 では *union types*(別名「discriminated unions」や「tagged unions」)が導入され、変数が複数の異なる名前付き代替値のいずれかを保持できるようになりつつ、型安全性も維持できます。 --- ### 1. ユニオン型の定義 ```csharp public union Result<TSuccess, TError> { // 成功の場合 public readonly record struct Ok(TSuccess Value); // エラーの場合 public readonly record struct Err(TError Reason); } ``` - `Result<int, string>` は `Ok`(整数)または `Err`(文字列)のいずれかを保持できます。 - 各代替値は *record struct* であるため、イミュータブルで軽量です。 --- ### 2. ユニオンの使用 ```csharp Result<int, string> GetNumber(bool success) { return success ? new Result<int, string>.Ok(42) : new Result<int, string>.Err("failed"); } var result = GetNumber(true); result.Match( ok => Console.WriteLine($"Success: {ok.Value}"), err => Console.Error.WriteLine($"Error: {err.Reason}") ); ``` - `Match` はパターンマッチングのヘルパーで、各代替値に対してデリゲートを受け取ります。 - C# の `switch` 式でも同様に扱えます: ```csharp var message = result switch { Result<int, string>.Ok(ok) => $"Success: {ok.Value}", Result<int, string>.Err(err) => $"Error: {err.Reason}" }; ``` --- ### 3. メリット | 機能 | 重要性 | |------|--------| | **型安全** | コンパイル時にすべての代替値が処理されることを保証します。 | | **イミュータブル** | record struct により不注意な変更を防止します。 | | **パターンマッチング** | 各ケースを簡潔かつ表現力豊かに扱えます。 | | **外部ライブラリ不要** | 言語内蔵であり、`OneOf` などのサードパーティパッケージは必要ありません。 | --- ### 4. よく使われるパターン - **Result / Either** – 成功 vs エラー(`Ok/Err`、`Right/Left`)。 - **Option / Maybe** – 値あるいはなし(`Some/Nothing`)。 - **状態機械** – 状態を個別の代替値として扱う。 ```csharp public union State { public readonly record struct Idle; public readonly record struct Running(int Count); public readonly record struct Stopped(string Reason); } ``` --- ### 5. 相互運用性 ユニオンを、別々の型を期待する API に渡す際はパターンマッチングで内部値を抽出できます: ```csharp if (result is Result<int, string>.Ok(ok)) { ProcessValue(ok.Value); // ok は Result<int,string>.Ok 型です } ``` --- ### 6. 留意点 - **シリアライゼーション** – カスタム処理が必要になる場合があります。ユニオンは自動的にシリアライズ可能ではありません。 - **パフォーマンス** – 各代替値は独立した struct であるため、高スループット環境では過度な箱詰めを避けるべきです。 - **ツールサポート** – IDE はユニオン型を理解しますが、古いツールでは対応していない可能性があります。 --- ### 7. クイックリファレンス ```csharp // 宣言 public union MyUnion { public readonly record struct A(int X); public readonly record struct B(string Y); } // インスタンス生成 var a = new MyUnion.A(10); var b = new MyUnion.B("hello"); // パターンマッチング myValue.Match( a => HandleA(a.X), b => HandleB(b.Y) ); // Switch 式 string result = myValue switch { MyUnion.A(a) => $"A: {a.X}", MyUnion.B(b) => $"B: {b.Y}" }; ``` --- **結論** C# 15 のユニオン型は、外部ライブラリのボイラープレートを排除しつつ、エラー処理や状態表現などで「複数形態の値」をクリーンにかつ型安全に扱うための強力な手段です。 | そっか~ニュース