C# がようやくユニオン型(Union Types)に対応しました。.NET (OK, C#) にて

2026/05/22 21:28

C# がようやくユニオン型(Union Types)に対応しました。.NET (OK, C#) にて

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

要約

Japanese Translation:

C# 15 および .NET 11 では、Windows、Linux、MacOS など複数の潜在的に無関係なレコード型を、基底クラスやタグ付き enum に依存せずに単一の変数内で表現できるようにする画期的な機能「union types」(新しい

union
キーワードを使用)が導入されました。デフォルトでは、union types は
IUnion
インターフェースを実装し、単一の
object? Value
プロパティを持っています。switch 式はすべての case を網羅している場合、明示的な discard case を必要とせずに union の各ケースを自動的に分解(deconstruct)できます。この利便性には代償があり、デフォルトの実装では値型が
object?
中に boxing され、ホットパスでヒープ割当を引き起こす可能性があります。開発者は、この問題を回避するために
HasValue
および
TryGetValue(out T)
メンバーによるカスタムの非 boxing 実装を提供するか、または
[Union]
属性を使用してコンパイラによる再書き換えを適用することで(例:
new MacOS(...)
のようなレコードを union wrapper に巻き込む)、暗黙的変換を回避できます。union を使用するには、.NET 11 プレビュー SDK をインストールし、
.csproj
<LangVersion>preview</LangVersion>
を設定するか、2026 年 5 月 19 日までの時点では
[Union]
属性の適用または言語バージョンの設定により earlier runtimes(例:.NET 8)をターゲットにします。IDE でのサポートは現在 Visual Studio Preview および VS Code C# DevKit Insiders で利用可能で、JetBrains Rider のサポートは待機中です。エコシステムが進化するにつれて、将来のアップデートでは閉じた enum、閉じた階層、union メンバープロバイダーを導入し、型安全性と網羅性チェックをさらに強化することで、Windows、Linux、MacOS 上で互換性のある開発環境を利用したより柔軟なクロスプラットフォームアプリケーションの実現を可能にします。

本文

#.NET 11(C# 15)で登場!ついにユニオン型を獲得しました

2026 年 5 月 19 日公開|所要時間:約 10 分

本シリーズ「#.NET 11 プレビュー版の探求」の第 2 部です。

  • 第 1 部 - Blazor で Web Workers を使用してバックグラウンドタスクを実行する
  • 第 2 部 - .NET(つまり C#)ついにユニオン型を獲得しました 🎉(本記事)

ユニオン型とは何か?

ユニオン型は、関数型プログラミングにおける基本的なデータ構造です。F#、TypeScript、Rust などの言語で広く利用されており、「一つの型として二つの異なるものを表現する」ための機能です。

代表的なパターン:
Result<T>

最も一般的な実装例の一つである

Result<T>
は、以下の 2 つの状態のみを持ちます:

  • Success(成功) - 操作の結果を含む
  • Error(エラー) - エラーメッセージなどの情報を含む

このパターンは「結果パターン(result pattern)」とも呼ばれ、呼び出し側が明示的に両方のケースを処理することを義務付けます。

柔軟な型の組み合わせ

ユニオン型は

Result
に限定されず、任意の組み合わせられた型セットを表現するために使用できます。例えば、「複数の潜在的に関連性のない型のいずれか」を表す場合に最適です。


C# 15 におけるユニオン型と
union
キーワード

C# 15(正式には C# 15)では、

union
キーワードによる直接サポートが導入されました。これにより、複数の異なる型のいずれかであるデータを格納できる型を定義できます。

多様なタイプの例

例えば、OS の情報を持つレコードを定義する場合:

public record Windows(string Version);
public record Linux(string Distro, string Version);
public record MacOS(string Name, int Version);

これらを単一の型で扱えるようになります。

新しい構文と
IUnion
インターフェース

// 👇 型として `union` を使用
public union SupportedOS(Windows, Linux, MacOS);
//             👆 ユニオンに含まれる型のリスト

生成された

SupportedOS
は、以下のインターフェースを実装します:

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

インスタンスの作成方法

  1. 明示的なコンストラクタ呼び出し
    SupportedOS os = new SupportedOS(new MacOS("Tahoe", 25));
    
  2. インプリシット変換(裏側でコンストラクタが呼ばれる)
    SupportedOS os = new MacOS("Tahoe", 25); // ✅ これも可
    

値へのアクセス:
switch

.Value
を経由して
object?
として取得することもできますが、標準的な処理は
switch
です。

string GetDescription(SupportedOS os) => os switch
{
    Windows windows => $"Windows {windows.Version}",
    Linux linux => $"{linux.Distro} {linux.Version}",
    MacOS macOS => $"MacOS {macOS.Name} ({macOS.Version})",
}; // 注:discard `_` は必要ありません

コンパイラーは自動で網羅性をチェックし、未処理のケースがある場合は警告が出ます。また、nullable なケース(例:

MacOS?
)には null チェックも含まれます。

Result<T>
も同じように実装可能

public union Result<T>(T, Exception);

または

Option<T>
型を定義することも可能です:

public record class None;
public union Option<T>(None, T);

.NET 11 におけるユニオン型の実用化

ユニオン型を使用するには以下の準備が必要です。

1. ソフトウェア要件

  • .NET 11 プレビュー版 2 以上の SDK のインストール
  • 推奨: プレビュー版 4+ を使用するとより安定した体験ができます

2. プロジェクト設定(
.csproj

言語機能を有効にするために、以下を追加します:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <!-- 👇 これを追加します -->
    <LangVersion>preview</LangVersion>
    <TargetFrameworks>net11.0;net8.0;net48</TargetFrameworks>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>
</Project>

3. 古いランタイムをターゲットする場合の対応

[Union]
属性や
IUnion
インターフェースが標準に含まれていない環境(プレビュー版 2/3、または古いランタイム)では、以下のコードを手動で追加する必要があります:

#if !NET11_0_OR_GREATER
namespace System.Runtime.CompilerServices;

[AttributeUsage(Class | Struct, AllowMultiple = false, Inherited = false)]
public sealed class UnionAttribute : Attribute;

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

IDE サポート状況

  • Visual Studio Preview / VS Code (C# DevKit Insiders): 初期サポートあり
  • JetBrains Rider: 未対応(待機中)

ユニオン型がどのように実装されているか

.NET 11
では、コンパイラーが自動的に簡素な構造体を生成します。

ビルドインの動作

宣言:

using System.Runtime.CompilerServices;

[Union]
public struct SupportedOS : IUnion
{
    public object? Value { get; }

    // 各ケースタイプに対するコンストラクター
    public SupportedOS(Windows value) => this.Value = (object) value;
    public SupportedOS(Linux value) => this.Value = (object) value;
    public SupportedOS(MacOS value) => this.Value = (object) value;
}

生成される型は以下の特性を持ちます:

  • [Union]
    属性で装飾された
    struct
  • 単一の readonly プロパティ
    object? Value
  • すべてのケースタイプに対するコンストラクター

インプリシット変換の裏側

new MacOS(...)
と書くと、コンパイラーは内部で
new SupportedOS(new MacOS(...))
に置き換えます。この挙動は
[Union]
属性によって制御されています。

属性を忘れた場合のエラー確認

もし

[Union]
属性を指定し忘れると、インプリシット変換や switch 式が機能しません:

// エラーが発生します
SupportedOS os = new MacOS("Tahoe", 25); 
// error CS0029: 型'MacOS'から'SupportedOS'へのインプリシット変換できません

var description = os switch
{
    Windows windows => "...", // error CS8121: パターンで処理できません
    // ...
};

カスタムユニオン実装でのボックス化の回避

ビルトインの実装は

object? Value
を使用するため、値が自動的にボックス化されます。これは
int
bool
などの単純な値を扱う場合にパフォーマンス面で望ましくありません。

ボックス化の問題

以下のコードでは、値がヒープ上に配置されてしまいます:

[Union]
public struct IntOrBool : IUnion
{
    public object? Value { get; }
    
    // Struct の引数は常にボックス化され、ヒープ上に割り当てられます
    public IntOrBool(int value) => this.Value = (object) value;
    public IntOrBool(bool value) => this.Value = (object) value;
}

回避方法:
TryGetValue
パターン

パフォーマンスを重視する場合、「非ボックス化」実装が推奨されます。これには以下の追加メンバーが必要です:

  1. bool HasValue { get; }
    :値が存在するか(null でないか)を判定
  2. bool TryGetValue(out T value)
    :型に応じて値を取り出す

カスタム非ボックス化実装の例

[Union]
public struct IntOrBool : IUnion
{
    private readonly bool _isBool;
    private readonly int _value;

    public IntOrBool(int value)
    {
        _isBool = false;
        _value = value;
    }

    public IntOrBool(bool value)
    {
        _isBool = true;
        _value = value ? 1 : 0; // 真偽値を 1/0 に変換して整数で管理
    }

    public bool HasValue => true;

    // ボックス化せずに int 値を取得
    public bool TryGetValue(out int value) 
    {
        value = _value;
        return !_isBool;
    }

    // ボックス化せずに bool 値を取得
    public bool TryGetValue(out bool value) 
    {
        value = _isBool && _value is 1;
        return _isBool;
    }
    
    // 👇 IUnion の要件を満たすが、通常は使用されません(ボックス化されるため)
    public object Value => _isBool ? (_value is 1) : _value;
}

スイッチ式での使用

TryGetValue
を実装すると、コンパイラーが自動でそれらを使用します:

IntOrBool unmatchedValue = new IntOrBool(23);
string str;

// 👇 ボックス化された Value プロパティの使用 대신 TryGetValue を呼び出します
if (unmatchedValue.TryGetValue(out int _)) 
{
    str = "integer";
}
else if (unmatchedValue.TryGetValue(out bool _))
{
    str = "bool";
}

その他、まだ来る予定の機能とは?

言語仕様にはまだ追加される予定の機能がいくつかあります:

  • ユニオンメンバープロバイダー
    ユニオン型自体とは異なる型でメンバを定義する方法
  • 閉じた列挙型(Closed Enums)
    Switch 式に「キャッチオールケース」(
    _ =>
    ) を含めなくてもよい列挙型
  • 閉じた階層(Closed Hierarchies)
    クラスに
    closed
    モディファイアを追加し、外部からの派生を禁止する機能

これらが .NET 11 に導入されるかは未定ですが、今後対応されたら必ずカバーします!


まとめ

本記事では以下について解説しました。

  • #.NET 11 プレビュー版 2 でユニオン型が正式にサポートされました。
  • union
    キーワードと
    [Union]
    属性による実装方法。
  • Switch 式(パターン Matching) を用いたデコンストラクションの実行方法。
  • インターフェース
    IUnion
    とプロパティ
    .Value
    の仕組み。
  • パフォーマンス向上のための**非ボックス化実装(TryGetValue)**の作成方法。

これにより、C# の網羅性(Exhaustiveness)が飛躍的に向上し、関数型プログラミングの手法がより安全に利用できるようになります。

同じ日のほかのニュース

一覧に戻る →

2026/05/24 3:45

私の Writerdeck を語る時が来ました。

## 日本語翻訳: 著者は、6年経った System76 Galago Pro ラップトップを「writerdeck」と名づけたオフライン書写ステーションに変換し、X11、Wayland、およびデスクトップ環境を排する tty ベースの構成で Debian Trixie を実行することでミニマリズムを優先しています。コンテンツが公開共有を目的としているためフルディスク暗号化は省略され、管理には `sudo` ユーザーモデルに切り替えて root ログインが無効化されました。本質的なツールとして、Neovim がテキスト編集に使用され(従来のエディタに代わり)、Debian バックポートからの `kmscon` でスケーラブルなターミナルウィンドウを可能にし、セッション多重化には `tmux` を使用し、インストール済みの Network Manager 経由の `nm-tui` が Wi-Fi/WAN の管理に用いられます。電力モニタリングおよび画面明るさ制御は `acpi` と `light` コマンドで行われ、自動ログインと `.bashrc`内の起動スクリプトにより Neovim が `tmux` セッション内で動作し、ブート時に Vimwiki が起動するようにしています。Syncthing は Vimwiki フォルダをリモートサーバーに同期させ、ブラウザ GUI を必要とせずオフラインファーストなワークスペースを維持するために全てのネットワークアドレスを活用しています。この構成はデジタルの雑多さを削減し、悪い書写習慣を打破助けるとともに、クリエイティブ専門家にとってセキュリティと生産性を向上させます。

2026/05/24 7:25

自分でロールを作るな

## 日本語訳: 要約: 開発者は、暗号化やユーザーインターフェースコンポーネントといった重要な機能に対して独自の実装を即時に停止する必要があります。なぜなら、「自分自身でつくる」というソリューションは過去の実績が証明する通り危険であるためです。最も重要な教訓は、安全性と使い勝手を確保するために、自作コードの代わりに既成でピアレビューされた標準を採用しなければならないことです。独自のカスタム暗号化パッケージには、初期化の不備や予測可能なパターンなどの深刻な欠陥を内包しており、規制産業では財務規制に違反し、高額の罰則を引き起こす可能性があります。セキュリティの問題だけでなく、ネイティブブラウザ要素を置換することは性能低下をもたらし、過剰な JavaScript ロジックによりキーボードスクロールの破損やリンクの読み込み遅延といった問題を引き起こします。さらに、独自のソリューションは自動入力機能など、安全なパスワード管理などの重要なネイティブ機能を排除してしまいます。この傾向は、組み込みブラウザ機能の安定性よりも創造的なツールの構築を優先しており、ユーザー(高齢者のご家族も含まれます)が慣れ親しんだツールを常に再学習することを強いられています。これらの使い勝手に関する落とし穴を防ぎ、規制当局による罰金を避けるためには、開発者はネイティブ要素を置換するのではなく補完する方向へ転換すべきです。これにより、すべてのウェブサイトで一貫した動作を確保できます。

2026/05/21 6:21

私の二画面デスクセットアップ(2025)

## 日本語訳: 著者は、壁を向いた単調なパソコンデスクを部屋の方を向くように回転させることで(ドアを見渡させ、奥行きを追加)、そしてテック専用だった単一の面を、二つの明確なゾーニングに分かれた大規模な USM ハラーデスクに置き換えることにより、ハイブリッドなワークスペースへと成功裡に変化させています。このデザインは、ソーシャルメディアでのフィードバックを受けてから以前の壁を向いた配置が持っていた「古い」外観に対応し、また、未使用のアイテム、おもちゃ、プロジェクトをアナログ側の面に置き続けることで、ストイックなミニマリズムの限界を解決し、アイデアを刺激することを可能にしています。単純にチェアを二つの半分の間で移動させることで強化された明確な精神的境界線により、このセットアップは一つの表面上で「作業」「思考」「子供たちと過ごす時間」という 3 つの異なる機能を発揮します。9 ヶ月の使用後、著者は単一コンピューターの配置に戻る予定はないことを確認しており、むしろミニマリスト的な規律とマキシマリスト的な柔軟性を融合させたこの柔軟なシステムを維持する意欲を持っています。これにより、別々の部屋を必要とせずに、ワークフローと創造的アウトプットを向上させることができます。

C# がようやくユニオン型(Union Types)に対応しました。.NET (OK, C#) にて | そっか~ニュース