マイクロソフトは現在、C#におけるメモリ安全性を飛躍的に向上させるための取り組みを本格化させています。

この中心となるのは、unsafeキーワードの役割を再定義し、開発者がコードの安全性をより厳密に管理できるようにする新しいモデルの導入です。

本記事では、2026年5月時点での最新情報に基づき、.NET 11でのプレビュー公開、および.NET 12での正式リリースが予定されている次世代のC#メモリ安全モデルについて詳しく解説します。

C#におけるメモリ安全性への新たなアプローチ

C#は誕生当初から、ガベージコレクションや配列の境界チェックなどの仕組みを通じて、高いメモリ安全性を備えた言語として設計されてきました。

しかし、低レベルのハードウェア操作やパフォーマンスの極限を追求する場合、開発者はunsafeキーワードを使用して、コンパイラの安全チェックをバイパスする必要がありました。

これまでのC#では、unsafeは主に「ポインタ機能の使用を許可する」という構文上のフラグとして機能してきました。

新しいモデルでは、unsafeの定義が拡張され、コンパイラが安全性を検証できない全ての操作をカプセル化するための「契約(Contract)」へと進化します。

これにより、これまで開発者の暗黙の了解や慣習に頼っていた安全性の担保が、コンパイラによって強制され、可視化された構造へと変わります。

新モデルの核となる「契約」と「義務」

新しいメモリ安全モデルでは、プログラムが「生存しているメモリ(Live Memory)」のみにアクセスすることを保証するという、一つの核心的な不変条件を維持することを目指しています。

安全なコード(Safe Code)では、コンパイラとランタイムがこの条件を自動的に保証します。

一方で、アンセーフなコード(Unsafe Code)においては、この責任が開発者へと移譲されることになります。

1. 内部unsafeブロックの必須化

新しいモデルを有効にすると、アンセーフな操作を実行する全てのコードは、明示的にunsafe { }ブロックで囲む必要があります。

たとえメソッドの署名にunsafe修飾子が付与されていても、その内部で実際にポインタをデリファレンス(参照解決)する箇所には個別のブロックが必要となります。

これにより、コードレビューにおいて「どこが危険な操作か」を即座に判別できるようになります。

2. 伝搬(Propagation)と抑制(Suppression)

このモデルにおいて、unsafeキーワードは二つの異なる方法で使用されます。

一つ目は「伝搬」で、メソッドの署名にunsafeを付与することで、その内部で行われている安全性の義務を呼び出し元(Caller)に引き継がせます。

二つ目は「抑制」で、メソッドの署名にunsafeを付けず、内部のunsafeブロックで義務を完結させることで、呼び出し元に対しては「安全なインターフェース」として振る舞います。

これは、RustやSwiftといったモダンなシステムプログラミング言語が採用しているアプローチと同様の考え方です。

3. 安全性のドキュメント化:/// <safety> タグ

新しいモデルでは、unsafeなメンバーには/// <safety>という新しいドキュメントコメントを記述することが強く推奨されます。

ここには、呼び出し元が安全性を維持するために満たさなければならない具体的な条件を記述します。

コンパイラや静的解析ツールは、このタグの有無をチェックし、適切な契約が結ばれているかを確認するようになります。

C# 16で導入される主要な変更点

新しい安全モデルは、名目上はC# 16の機能として計画されており、従来のC# 1.0以来のルールを大幅にアップデートします。

以下に、主要な変更点をまとめます。

項目従来のモデル(C# 1.0〜)新しいモデル(C# 16〜)
unsafe修飾子の対象クラス、構造体、メソッド等メソッド、プロパティ、フィールド(型レベルは廃止)
安全性の伝搬ポインタ型の使用によってのみ判断メソッド署名のunsafe修飾子で明示的に制御
呼び出し側の制約unsafeコンテキストがあれば自由に呼べる呼び出しごとに個別のunsafeブロックが必要
安全性の証明慣習やコメントによる説明<safety>タグによる形式的な契約

特に注目すべきは、型レベルのunsafe修飾子がエラーになる点です。

これからは、型全体を「アンセーフ」としてマークするのではなく、個別のメンバーに対して精密に安全性の境界を設定することが求められます。

具体的なコード例による新旧の比較

新しいモデルがどのようにコードの書き方を変えるのか、具体的な例を見ていきましょう。

以下は、従来のMarshal.ReadByteに似た処理を新モデルで実装した場合のイメージです。

C#
/// <summary>アンマネージメモリから1バイトを読み取ります。</summary>
/// <safety>
/// ptrとofsの合計が、呼び出し側が読み取りを許可されているメモリ範囲内である必要があります。
/// </safety>
public static unsafe byte ReadByte(IntPtr ptr, int ofs)
{
    // IntPtrをポインタにキャスト(これは安全な操作として扱われる)
    byte* addr = (byte*)ptr;

    unsafe
    {
        // SAFETY: 呼び出し側の義務(契約)に従い、範囲内であることを前提にデリファレンスを実行
        // この「unsafe」ブロックが、危険な操作の明示的な目印となる
        return addr[ofs];
    }
}

次に、このメソッドを呼び出す側のコードを見てみましょう。

C#
public void ProcessData(IntPtr buffer, int size)
{
    // 入力値の検証(ガード)を行う
    if (buffer == IntPtr.Zero || size <= 0) throw new ArgumentException();

    unsafe
    {
        // SAFETY: 事前のチェックにより、メソッドの安全契約を満たしていることを保証する
        byte value = ReadByte(buffer, 0);
    }
}

従来のC#では、ReadByteの呼び出し側は特に追加の記述なしで実行できましたが、新モデルでは「安全性の義務を認識し、それを果たした」ことを示すためのunsafeブロックが呼び出し側にも必須となります。

プロジェクトでの導入方法と移行支援

この新しい安全モデルは、既存の全てのプロジェクトに強制されるわけではありません。

当初はプロジェクトレベルの新しいプロパティ設定による「オプトイン(選択制)」として提供されます。

既存のコードを維持したい場合は、従来のC# 1.0互換のルールが適用され続けます。

しかし、マイクロソフトは将来的にこのモデルを標準にすることを目指しており、新しいプロジェクトテンプレートでは既定で有効化される予定です。

また、大量の既存コードを持つ開発者のために、dotnet formatツールを拡張し、コードの機械的な書き換えを支援する機能も計画されています。

このフィクサー(修正機能)は、アンセーフな呼び出し箇所を自動的にブロックで囲んだり、型レベルの修飾子をメンバーレベルへ移動させたりする作業を自動化します。

ただし、/// <safety>タグの内容を記述するなどの「論理的な安全性の説明」は、引き続き開発者の手によって行われる必要があります。

なぜ今、メモリ安全性が重要なのか

近年、IT業界全体でメモリ安全性の優先順位が急速に高まっています。

CISA(米国サイバーセキュリティ・インフラセキュリティ庁)などの政府機関も、脆弱性の大部分がメモリ関連のミスに起因しているとして、メモリ安全な言語の使用を推奨しています。

また、AIによるコード生成の普及も大きな要因です。

AIが生成するコードは大量かつ高速ですが、その安全性を人間が全てレビューするのは困難になりつつあります。

コンパイラが「どこが危険か」を厳格に要求する言語仕様を備えることで、AI生成コードの安全性チェックを自動化・効率化することが可能になります。

C#がこの新しいモデルを採用することで、.NETエコシステム全体の堅牢性がさらに高まることが期待されています。

まとめ

今回のC#におけるメモリ安全性の強化は、単なる機能追加ではなく、言語としての哲学の大きな進化と言えます。

unsafeキーワードを、単なる「警告を消すための手段」から「開発者とコンパイラ間の明示的な契約」へと昇華させることで、より安全で保守しやすいソフトウェア開発が可能になります。

.NET 11でのプレビュー開始に向けて、ランタイムライブラリ自体もこの新しいモデルへの移行が進められており、その成果は私たちのアプリケーションにも大きな恩恵をもたらすでしょう。

メモリ安全性の向上は、パフォーマンスを犠牲にすることなく、開発者がより自信を持って高度な最適化に挑戦できる環境を提供します。

来るべき.NET 11、12のリリースに向け、今からこの新しい安全モデルの考え方を理解し、準備を進めておくことは、全てのC#開発者にとって極めて価値のある投資となるはずです。