安全でない進化

チャンピオン号: https://github.com/dotnet/csharplang/issues/9704

まとめ

C# の unsafe の定義を、ポインター型が使用される場所を参照しないように更新し、ランタイムによってアンマネージ メモリが逆参照される場所にします。 これらの場所は、メモリセーフが発生する場所であり、メモリの安全性の問題として分類される CVE (一般的な脆弱性と露出) の大部分を担当します。

// Under the proposed rules:
void M()
{
    int i = 1;
    int* ptr = &i; // Allowed: creating a pointer is not itself unsafe
    unsafe
    {
        Console.WriteLine(*ptr); // Dereference of memory not managed by the runtime. This is unsafe.
        ref int intRef = Unsafe.AsRef(ptr); // Conversion of memory not managed by the runtime to a `ref`. This is unsafe.
    }
}

namespace System.Runtime.CompilerServices
{
    public static class Unsafe
    {
        unsafe public static ref T AsRef<T>(void* source) { /* ... */ } // `unsafe` marks the member as *requires-unsafe*.
    }
}

モチベーション

この機能の背景は、この提案の一部として必要となる広範なエコシステムの変化を追跡する https://github.com/dotnet/designs/blob/main/accepted/2025/memory-safety/caller-unsafe.mdでも確認できます。 これには、メソッドに安全でないものとして適切に注釈を付けるための BCL 更新や、メモリの安全でない場所をよりよく理解するためのツールの更新が含まれます。 具体的には、C# では、メモリの安全が言語によって適切に追跡されていることを確認します。今日では、プログラムを総合的に見て、メモリの安全が問題が発生するすべての場所を理解することは困難な場合があります。 これは、 System.Runtime.CompilerServices.UnsafeSystem.Runtime.InteropServices.Marshalなど、さまざまなヘルパーがメモリの安全性に違反し、特別な考慮が必要であることを表していないためです。 これらのヘルパーを使用するメソッドはすぐには明らかではありません。また、メモリの安全性の問題についてコードを監査する場合 (レビューを行うとき、または報告されている脆弱性の原因を特定しようとしたときに)、問題の原因となる可能性のある場所を特定することは困難な場合があります。

これまで、C# の unsafe は、特定のメモリセーフ ホール (ポインター型の存在) を参照してきました。 ポインター型が関与しなくなった瞬間、C# はメモリの安全でない状態をコードに潜らせるために完全に満足しています。 この問題は、C# と .NET エコシステムのunsafeのこの進化に対処し、メモリの安全でない可能性がある領域にラベルを付け、レビュー担当者や監査者がプログラム内の潜在的なメモリセーフの境界を理解しやすくするために探しています。 重要なのは、単に拡張するだけでなく、の意味をunsafeえるということです。 ポインターの存在自体は安全ではありません。安全でないアクションがポインターを逆参照しています。 これはさらに型自体まで拡張されます。型は本質的に安全ではありません。 これは、安全でない可能性がある型を使用する操作であり、その型の存在ではありません。

したがって、この情報がシステム内を流れるには、メソッド自体を unsafeとしてマークする方法が必要です。 現在、メソッド修飾子として unsafe は外部に影響を与えず、メンバーのシグネチャと本文でのみポインターを使用できます。 今後、修飾子として unsafe は、メンバーの意味を実際にパブリックに変更します。メンバーにメモリの安全性に関する懸念事項があり、そのメンバーを使用してプログラマが使用を手動で検証する必要があることを示します。 これは、 unsafeの既存の意味の拡張です。本文の unsafe は、その本文に監査義務をローカライズしますが、署名に unsafe すると、その義務が呼び出し元に拡張されます。

これは、C# ユーザー ベースの特定のセグメントに大きな破壊的変更が生じる可能性があります。 私たちの希望は、多くのユーザーにとって、これは効果的に透過的であり、新しいルールへの更新がシームレスになることです。 ただし、リフレクションの大きな部分のようないくつかの大きな API サーフェスを unsafeマークする必要がある場合は、エコシステムが完全に分岐しないように、新しいルールに適切なオンランプが必要になる可能性があると考えられます。

重大な変更

この言語機能を実装するコンパイラに更新するときに、次の重大な変更を確認できます。

  • 更新されたメモリセーフルールが有効になっている場合(将来の.NETバージョンではデフォルトまたは唯一のオプションである可能性があります):
    • unsafe メンバーに対しても、それを 必須セーフとしてマークするようになりました。つまり、呼び出し元は unsafe コンテキストに存在する必要があり、基本メンバーが安全な場合はオーバーライドを unsafe できません。
    • unsafe メンバーまたは型では、 unsafe コンテキストが自動的に導入されません。つまり、明示的な unsafe ブロックは、メンバー本体と初期化子 unsafe 操作の周囲で使用する必要があります。
    • 明示的なレイアウトexternメンバーとフィールドには、明示的なunsafe/safeが必要です宣言のキーワード。
    • stackalloc 特定の条件下では、unsafeコンテキストが必要です。
    • unsafe 修飾子は、型宣言、静的コンストラクター、デストラクターに関するエラーです。これは効果がないためです。
  • 新しい言語の下:
    • ラムダ推論では、より多くの候補が考慮され、オーバーロードの解決 があいまいになる可能性があります。
    • safe はコンテキスト キーワードになりました。これにより、型として使用されたコードが 中断 される可能性があります。
    • レガシ モードの互換性モードでは、 エラーが増える可能性があります。

詳細な設計

用語: メンバーを呼び出す 場合は、アンセーフが必要 です (以前は 呼び出し元の安全でないと呼ばれます)

構文

この提案では、次の内容が紹介されています。

  • safeのメンバーとフィールドに適用できる新しいexternコンテキスト キーワード
  • および unsafe 式 (unsafe(expression))、 安全でない式で詳しく説明されています。

この新しい構文は、新しい LangVersion で使用できますが、オプトインに関係なく、 オプトイン時に必要な操作を行う必要がある場合は、オプトインする前に実行を許可されるという前提で行います。

既存の unsafe ルール

既存の C# 仕様には、 unsafeに特化した大きなセクションがあります: §24 Unsafe code。 これは、有効な C# コンパイラが unsafe 機能をサポートするために必要ではないため、条件付きで規範的として定義されます。 ポインターの定義のほとんどはそれ自体が安全でないと見なされなくなったため、現在条件付きで規範的と見なされているものの多くは、この変更後はそうではなくなります。 ポインター型固定変数と移動可能変数、すべての ポインター式 ( ポインター間接参照ポインター メンバー アクセスポインター要素アクセスを除く)、 および fixed ステートメント はすべて unsafeと見なされなくなり、 unsafe コンテキストで使用する必要のない通常の C# に存在します。 同様に、 固定サイズのバッファー または初期化された stackalloc を宣言することは、安全な C# でも完全に有効です。 いずれの場合も、安全でないメモリにのみ アクセス します。

重要なのは、これらのポインター緩和は、アセンブリが更新されたメモリ安全規則オプトインするかどうかに関係なく適用されます。 指し示されたメモリに実際に逆参照する操作または直接アクセスする操作のみが、引き続き unsafe コンテキストを必要とします。 これにより、増分移行パスを有効にします。ユーザーは、メンバーが内部的にリスクを処理するときに unsafe を内側に移動するか、呼び出し元が監査に参加する必要があるときは外側に移動してから、アセンブリ全体のオプトイン スイッチを反転することで、既存のコードを整形できます。

この変更に固有の unsafe コード セクションとその他のパーツ C# 仕様の両方が広範に書き直された場合、扱いにくく、仕様の既存の規則の 1 行ずつの相違を提供するのは役に立たない可能性があります。 代わりに、特定のセクションで行う変更の概要と、 unsafe コンテキストで許可される内容に関する特定の新しい規則について説明します。

安全でないコンテキストを必要とする式の再定義

次の式を使用するには、 unsafe コンテキストが必要です。

これらの式に加えて、式とステートメントは、unsafeとしてマークされているシンボルに依存している場合、条件に応じてunsafeコンテキストを必要とすることもできます。 たとえば、安全でない必要があるメソッドを呼び出すと、 コンテキストが必要になります。 呼び出しが埋め込まれたステートメント (usingforeachなど) も、unsafeメンバーを使用する場合にコンテキストを必要とする場合があります。

このドキュメントで "安全でないコンテキストが必要です" などと言う場合は、コンストラクトで unsafe コンテキストを使用する必要があるというエラーを出力することを意味します。

Note

このセクションでは、各式とステートメントが unsafe コンテキストを必要とするために考慮する必要がある内容を正式に宣言するために拡張が必要な場合があります。

ポインター型

前述のように、ポインターは本質的に安全ではなくなります。 §24.3 の安全でないコンテキストへの参照はすべて削除されます。 ポインター型は通常の C# に存在し、存在させるために unsafe は必要ありません。 型定義は、他の型として §8.1 とその後のセクションで動作する必要があります。

同様に、 ポインター変換§10 に組み込み、 unsafe コンテキストへの参照は削除する必要があります。

同様に、ポインターの間接参照ポインター メンバー アクセス、ポインター要素のアクセスを除くポインターは§12 に組み込み、unsafeコンテキストへの参照を削除する必要があります。 これらの式の意味に関するセマンティクスは変更されません。唯一の変更は、使用する unsafe コンテキストが不要になったということです。

ポインター間接参照ポインター メンバー アクセスおよびポインター要素アクセスの場合、これらの演算子は、ランタイムを管理していないアクセス メモリであるため、安全ではありません。 これらは §24 のままであり、引き続きunsafeコンテキストを使用する必要があります。 unsafe コンテキストの外部で使用すると、エラーになります。 これらの演算子に関するセマンティクスは変更されません。彼らはまだ彼らが今日行うのとまったく同じことを意味し続けます。 これらの式は、常に unsafe コンテキストで発生する必要があります。

固定ステートメント§13 に移動し、unsafe コンテキストへの参照は削除されます。

関数ポインターはまだ C# のメイン仕様に組み込まれていませんが、同様の影響を受けます。関数ポインター呼び出し以外はすべて標準仕様に移動されます。 関数ポインター呼び出し式は、常に unsafe コンテキストで発生する必要があります。

固定サイズ バッファー

固定サイズ バッファーのストーリーはポインターに似ています。 固定サイズバッファーの定義自体は危険ではなく、 §16.3 に移動します。 式がelement_accessとして発生しない限り、式内の固定サイズ バッファーへのアクセスも同様に安全です。これらは、上記の規則に従って安全ではないpointer_element_accessとして評価されます。

スタックの割り当て

繰り返しますが、 スタック割り当ての ストーリーは ポインターによく似ています。 stackallocをポインターに変換しても安全ではなくなりました。安全でないのは、そのポインターの遅延です。 ただし、新しいルールを 1 つ追加します。

次のステートメントがすべて当てはまる場合、 stackalloc_expression は安全ではありません。

  • stackalloc_expressionは、Span<T>またはReadOnlySpan<T>に変換されています。
  • stackalloc_expressionにはstackalloc_initializerがありません。
  • stackalloc_expressionは、SkipLocalsInitAttribute適用されているメンバー内で使用されます。

これらのコンテキストでは、結果のスタック領域に不明なメモリの内容が含まれる可能性があり、アンマネージ メモリ アクセスに関する安全なラッパーを提供する型に変換されています。 これは Span<T>ReadOnlySpan<T>の契約に違反するため、このようなコードの作成者とレビュー担当者による追加の調査の対象となる必要があります。

緩和 unsafe ルールに対する他の変更とは異なり、これは締め付けであるため、中断を回避するために、更新されたメモリ安全規則へのオプトインでのみ適用されます。

Note

つまり、コンテキストに関係なく、ポインターに stackalloc を割り当てることは 常に 安全です。

マネージド型の stackalloc はエラーのままであることに注意してください。

sizeof

特定の定義済み型の場合、 sizeof は常に一定で安全であり (§12.8.19)、変更されません。 その他の種類の場合、 sizeof 安全でないコンテキスト (§24.6.9) を必要とするために使用されますが、更新されたメモリ安全規則へのオプトインに関係なく安全になりました。

オーバーライド、継承、実装

呼び出し元が基本定義を使用していて、派生実装によるunsafeの追加が表示されない可能性があるため、最初は安全でないメンバーのオーバーライドまたは実装でメンバー レベルでunsafeを追加するのはメモリセーフ エラーです。

デリゲートとラムダ

必須の安全でないメンバーを、unsafe コンテキスト外のデリゲート型に変換するのはメモリセーフ エラーです。 デリゲート型と 関数型、必ず安全ではありません。 ラムダ シンボルに unsafe を適用するのはコンパイル時エラーです。

extern

externメソッドはランタイムで保証できないネイティブの場所であるため、コンパイラは安全か安全でないかを判断できません。 unmanagedパラメーターのみを値で受け取るメソッドであっても、C# によって安全に呼び出すことはできません。これは、メソッドに使用される呼び出し規則がユーザーによって誤って指定される可能性があり、レビューによって手動で検証する必要があります。

そのため、更新されたメモリ安全規則の下で、コンパイラでは、各 extern メソッドを明示的に unsafe または safeとしてマークする必要があります。

extern unsafeはパブリック サーフェスの一部ではない実装の詳細と見なされるため、レガシ メモリ安全規則を使用するアセンブリからのメソッドは暗黙的にexternとは見なされません。 extern は、参照アセンブリに保持される保証はありません。

これはレガシ ルール アセンブリにも適用される 互換性モード とは異なる点に注意してください。これは、シグネチャにポインターを持つメソッドには、呼び出しサイトで常に安全でないコンテキストが必要になるためです。

安全でない修飾子とコンテキスト

現在、 安全でないコンテキストの仕様で説明されているように、 unsafe は字句的な方法で動作し、 unsafe ブロックに含まれるテキスト本文全体を unsafe コンテキスト (反復子本体を除く) としてマークし、宣言の場合は周囲のコンテキストもいくつかマークします。

class A : Attribute
{
    public A(object o) { }
}
class C
{
    [A(default(int*[]))] void M1() { } // error: using pointers outside `unsafe` context
    [A(default(int*[]))] unsafe void M2() { } // ok
}

更新されたメモリ安全規則にオプトインすると、メンバーのunsafeは、それを必須セーフとしてマークし、呼び出し元に監査義務を拡張し、コンテキストを導入unsafe (代わりに、本文の明示的なunsafe領域のみがunsafeコンテキストを確立します)。

unsafe 次の宣言では、意味がなくなったため、エラーが発生します。

  • delegate,
  • static コンストラクター、
  • デストラクター、
  • 型宣言 (classstructなど)。

unsafe コンストラクターでは、初期化子内に unsafe コンテキストが導入されます。つまり、 unsafe コンストラクターは 、requires-unsafebase または this コンストラクターを呼び出すことができます。

パラメーターなしの 必須の安全でない コンストラクターを持つ型は、 new() 制約を満たしていません。 同様に、パラメーターなしの 必須の安全でない コンストラクターを持つ構造体も、 struct 制約を満たしていません。

unsafe は、メンバー内の入れ子になった匿名関数またはローカル関数には適用 されませんunsafe ブロック内で宣言された匿名関数とローカル関数も同様です (これらは常にunsafeコンテキストにありますが、必ずしも安全ではありません)。 ローカル関数を requires-unsafe としてマークするには、手動で unsafeとしてマークする必要があります。 ラムダを 必須セーフ としてマークすることはできません ( unsafe キーワードは許可されていません)。

メンバーが partialされている場合、両方の部分が、現在の C# 規則とは変わらず、 unsafe 修飾子に同意する必要があります。

partial class C1
{
    public partial void M1(); // Error: both parts must be unsafe, or neither can be
    public partial unsafe void M2();
}

partial class C1
{
    public unsafe partial void M1() => Console.WriteLine("hello world");
    public partial void M2() => Console.WriteLine("hello world"); // Error: both parts must be unsafe, or neither can be
}

プロパティの場合、 get アクセサーと set/init アクセサーは個別に unsafe として宣言できます。プロパティ全体を unsafe としてマークすると、 get アクセサーと set/init アクセサーの両方が安全でなくなります。 現在、イベント アクセサーに修飾子を配置することはできません。この提案では、 add イベント アクセサーと remove イベント アクセサーを unsafeとして個別に宣言することはできません。 イベント全体が unsafeとしてマークされている場合にのみ、アクセサーは安全でありません。それ以外の場合は安全です。

Fields

unsafe フィールドに対しても 、必須の安全でないとマークされ、初期化子に unsafe コンテキストは導入されません。 互換性モードはフィールドにも適用されます。

プロパティまたはイベントを unsafe としてマークしても、バッキング フィールド には安全ではありません

[StructLayout(LayoutKind.Explicit)]または[ExtendedLayout]を持つ型では、すべてのインスタンス フィールドをsafeまたはunsafeマークする必要があります。 フィールドが自動プロパティまたはフィールドに似たイベントの背後にある "非表示" の場合、 safe/unsafe の要件は、代わりに自動プロパティまたはフィールドに似たイベントに移動されます。

Metadata

アセンブリが新しいメモリ安全規則を使用してコンパイルされると、言語バージョンとしてMemorySafetyRulesAttributeが入力された15 (以下で詳しく説明します) でマークされます。 これは、アセンブリで定義されているすべてのメンバーが、アセンブリを呼び出すためにRequiresUnsafeAttribute コンテキストが必要な場合に、unsafe (以下で詳しく説明) で適切に属性付けされることをダウンストリーム コンシューマーに通知するものです。 RequiresUnsafeAttributeでマークされていないアセンブリ内のメンバーは、メンバーのシグネチャ内の型に関係なく、unsafe コンテキストを呼び出す必要はありません。

ソース内のシンボルに明示的に MemorySafetyRulesAttribute または RequiresUnsafeAttribute を適用するとエラーになります。

コンパイラは、レガシ メモリの安全性規則を使用しているアセンブリから RequiresUnsafeAttributeマークされたメンバーを無視します (代わりに、互換性 モード が使用されます)。

型以外のメンバーが unsafe としてマークされている場合、コンパイラはメタデータ内のメンバーに対して RequiresUnsafeAttribute アプリケーションを合成します。 ユーザー側 の必須の安全でない メンバーが、自動プロパティの get/set メソッドなどの非表示のメンバーを生成する場合、ユーザー側メンバーと、そのユーザー側メンバーによって生成された非表示メンバーの両方がすべて 必須セーフであり、 RequiresUnsafeAttribute がそれらのすべてに適用されます。

MemorySafetyRulesAttributeRequiresUnsafeAttributeの定義は、必要に応じて、標準の既知のメンバー 規則に従ってコンパイラによって合成されます。

namespace System.Runtime.CompilerServices
{
    /// <summary>Indicates the language version of the memory safety rules used when the module was compiled.</summary>
    [AttributeUsage(AttributeTargets.Module, Inherited = false)]
    public sealed class MemorySafetyRulesAttribute : Attribute
    {
        /// <summary>Initializes a new instance of the <see cref="MemorySafetyRulesAttribute"/> class.</summary>
        /// <param name="version">The language version of the memory safety rules used when the module was compiled.</param>
        public MemorySafetyRulesAttribute(int version) => Version = version;
 
        /// <summary>Gets the language version of the memory safety rules used when the module was compiled.</summary>
        public int Version { get; }
    }

    [AttributeUsage(AttributeTargets.Event | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Constructor, AllowMultiple = false, Inherited = false)]
    public sealed class RequiresUnsafeAttribute : Attribute
    {
    }
}

互換性モード

互換性のために、および新しいルールを有効にするときに発生する誤った否定の数を減らすために、新しいルールに更新されていないモジュールのフォールバック ルールがあります。 このようなモジュールの場合、メンバーには、パラメーター型または戻り値の型のどこかにポインターまたは関数ポインター型が含まれている場合は、必須 と安全でない と見なされます ( int*[]など、ポインター以外の型で入れ子にすることができます)。 以前の呼び出しサイトでも安全でないコンテキストを必要としないため、これは制約型 ( where T : I<int*[]> など) のポインターには適用されません。

ターゲット メンバーが何かのためにポインター型を使用するタイプ セーフな方法がないため、これには、置換されたジェネリック パラメーター (例: I<T>.M(T)Tに置き換えた場合のメソッド int*[]) は含まれません。

このような互換性モードでは、 安全でない メンバーは、更新されたメモリ安全規則をオプトインしていない呼び出し元からでも、 unsafe コンテキストを使用する必要があります。 これにより、LangVersion を更新するだけで (ただし、メモリセーフ ルールのバージョンを更新しない)、ほとんどのポインター操作が安全になる "dip" を回避する必要があります (更新された規則にオプトインすると、必要 な安全でない とマークされる可能性が高いシグネチャ内のポインターを持つ関数の呼び出しを含む)、そのため、この移行ウィンドウでコードの保護が軽減されます。

VB

現在、VB にはコンテキストがなく、ポインターを操作する方法もないため、unsafe メンバーのVisual Basicのサポートを追加する必要はありません。

unsafe 表現

unsafe式では、1 つの式を評価するための最小限のunsafe コンテキストが導入されます。 これは、 unsafe ブロックが unsafe スコープを不必要に広げる場合や、 unsafe ブロックを構文的に使用できない場合 ( catch フィルター、フィールド初期化子、コンストラクター初期化子、 awaitのオペランド位置など) に役立ちます。

構文

unsafe_expressionprimary_no_array_creation_expressionとして追加されます。

unsafe_expression
    : 'unsafe' '(' expression ')'
    ;

他の場所の AllowUnsafeBlocks キーワードと同じunsafe要件が適用されます。

意味論

unsafe_expressionは、式を評価するためのunsafe コンテキストを確立します。 つまり、ポインターの逆参照、関数ポインター呼び出し、および requires-unsafe メンバーの呼び出しはすべて、囲まれた式内で許可されます。 unsafe_expressionの型と値は、囲まれた式の型と値です

unsafeによって確立されたunsafe_expression コンテキストは、閉じかっこを超えて拡張されません。

動機と移行の例

いくつかの構文上の位置では、 unsafe ブロックをまったく許可しませんが、必要 な安全でない メンバーを呼び出す部分式が含まれている可能性があります。 unsafe式がない場合、このようなコードを移行するには、安全でないサブ式をヘルパーローカル関数または一時変数に抽出する必要があります。これにより、意図が隠され、詳細度が向上します。

await 必要な安全でないメソッドの場合は〘。 await式は、unsafe ブロック内に表示できません。 待機中のメソッドが 必須セーフになると、タスクを待機する前に一時変数がタスクを保持する必要があります。

// Without unsafe expressions: must spill to a temporary
Task t;
// SAFETY: Discharges obligations because reasons
unsafe { t = DoWork(); }
await t;

// With unsafe expressions: the unsafe context wraps only the call;
// the await remains outside it and is fully legal
// SAFETY: Discharges obligations because reasons
await unsafe(DoWork());

フィルターをキャッチします。 when句のcatch フィルターは式であり、ステートメント本体ではありません。 unsafe ブロックはステートメントのみを囲むことができるため、フィルター式だけを囲む場所はありません。 さらに、 try 本文に await 式が含まれている場合、 unsafe ブロックは try/catch ステートメント全体を囲むことはできません。await は、 unsafe ブロック内では許可されません。 フィルターで使用されるメソッドが 必須で安全ではなくなった場合、 unsafe 式を使用しない唯一の代替手段はヘルパーです。

// Without unsafe expressions: must spill to a local function
static bool FilterHelper(Exception e)
{
    // SAFETY: Discharges obligations because reasons
    unsafe { return NowUnsafeCall(e); }
}

try
{
    await DoWork(); // 'await' here prevents wrapping the whole try/catch in 'unsafe'
}
catch (Exception e) when (NowUnsafeCall(e))
{
}

// With unsafe expressions: inline and minimal scope
try
{
    await DoWork();
}
// SAFETY: Discharges obligations because reasons
catch (Exception e) when (unsafe(NowUnsafeCall(e)))
{
}

フィールド初期化子。 更新されたルールでは、フィールドの unsafe は初期化子に unsafe コンテキストを導入しません。 フィールドの初期化子が requires-unsafe メンバーを呼び出すと、 unsafe 式はヘルパー メソッドを必要とせずにコンテキストを提供します。

// Without unsafe expressions: must spill to a helper method
static int InitialValue()
{
    // SAFETY: Discharges obligations because reasons
    unsafe { return ReadFromPointer(); }
}
static int _value = InitialValue();

// With unsafe expressions: inline
// SAFETY: Discharges obligations because reasons
static int _value = unsafe(ReadFromPointer());

コンストラクター初期化子。 this(...) および base(...) 初期化子引数リストは式であり、ステートメント本体ではありません。 これらの引数の 1 つが 必須の安全でない メンバーを呼び出す場合、 unsafe ブロックを挿入する場所はもう一度ありません。そのため、呼び出しはヘルパーに移動する必要があります。

class C(int x)
{
    // SAFETY: Discharges obligations because reasons
    C() : this(unsafe(GetUnsafeValue()))
    {
    }
}

class Derived : Base
{
    // SAFETY: Discharges obligations because reasons
    Derived() : base(unsafe(GetUnsafeValue()))
    {
    }
}

インライン呼び出し。 より一般的には、 requires-unsafe サブ式のみをラップすると、監査スコープが厳密に保たれ、周囲の安全なコード (引数の評価や包含メソッド呼び出しなど) が unsafe コンテキストに引き込まれるのを回避できます。

extern int Add(int i1, int i2); // Some fancy extern addition function

// Code I want to write:

// SAFETY: Discharges obligations because reasons
Console.WriteLine(unsafe(Add(1, 2)));

// Code I have to write without unsafe expressions, option 1
// (unsafe context unnecessarily includes the WriteLine call):

// SAFETY: Discharges obligations because reasons
unsafe
{
    Console.WriteLine(Add(1, 2));
}

// Code I have to write without unsafe expressions, option 2
// (very verbose and harder to read):
int result;
// SAFETY: Discharges obligations because reasons
unsafe
{
    result = Add(1, 2);
}
Console.WriteLine(result);

質問

(回答) RequiresUnsafeAttribute を使用して 、requires-unsafe メンバーを 示す

メンバーで unsafe キーワードを使用して 必須の安全でない メンバーを示す代わりに、メンバーに適用される属性 (RequiresUnsafeAttribute) を使用できます (メンバーの unsafe 修飾子の意味は変更されません)。

unsafeの利点:

  • 他の言語に似ているため、理解しやすくなります。
  • 属性よりも検出可能です。

属性 (または別のキーワード) の利点:

  • は、 unsafeとしてマークされた既存のメンバーを壊すのを回避します。
  • 増分導入が可能 (メンバーごとの)
  • では、全身をunsafeとしてマークする必要はありません (unsafeキーワードを使用してもunsafe体に影響を与えません)。
  • では、メンバー自体を requires-unsafe としてマークする必要なく、すべての requires-unsafe エラーを抑制できます ()。

議論:

回答: キーワード unsafe を使用して、 requires-unsafe メンバーを 示します。

ローカル関数/ラムダ セーフ コンテキスト

現在、メソッド本体の unsafe は字句スコープです。 入れ子になったローカル関数またはラムダはこれを継承し、その本体はメモリの安全でないコンテキストにあります。 この動作は言語で保持しますか? 呼び出し元が安全でない必要があることを公開するために使用される修飾子として unsafe を保持すると、メソッドのシグネチャに影響を与える可能性があることに注意してください。 現在提案されているように、入れ子になった匿名関数とローカル関数は、その包含メンバーの安全でないコンテキストを保持しません。

デリゲート型 unsafety

デリゲート型とラムダ (および関数型) を requires-unsafe としてマークすることができます。 これには、( unsafe コンテキスト外で) いくつかの追加の規則が必要になります。

  • requires-unsafe デリゲートを型引数として使用することを禁止します。
  • これらのデリゲートを 安全でない (DelegateExpressionobject) に変換することを禁止します。これを行わないと、 unsafe 注釈が間違った場所に強制され、実際の領域 unsafe適切に呼び出されない領域が発生するリスクがあります。

ラムダ/メソッド グループから安全なデリゲート型への変換

ラムダとデリゲートunsafe許可する場合、必要な安全でないラムダまたはメソッド グループを、コンテキストで警告またはエラーなしで許可されるunsafeデリゲート型に変換する必要がありますか? これを行わないと、エコシステムのさまざまな部分、特に LINQ クエリを通じて渡される列挙体にとって、かなり痛みを伴う可能性があります。

ラムダ/メソッド グループの自然型

現在、セマンティクスと codegen (追加のメタデータ以外) への唯 一の実際 の影響は、 unsafe がシグネチャにあるときにラムダまたはメソッド グループのfunction_typeを変更することです。 これを回避する場合、どちらにも本当の影響はなく、採用者は、内部で動作が微妙に変わっていないという確信を高めることができます。

Note

ラムダを unsafe する機能を維持する場合は、最初にラムダを unsafe 宣言できるように構文の変更を含むように、この提案を更新する必要があります。

マネージド型へのポインター

C# 11 では、警告を含むマネージド型へのポインターを使用できます。 アドレス指定操作の警告を緩和する必要がありますか? この問題は、ユーザーがこのようなポインターを逆参照した場合にのみ発生すると考えています。これは、通常の安全でない進化ルールに該当します。 しかし、 sizeofはどうでしょうか?

stackalloc 初期化済み

現在、 仕様では 常に stackalloc メモリが初期化されていないと見なされ、手動でクリアまたは割り当てられていない限り、内容は未定義であると言われています。 これは仕様のバグと考えていますか。それとも、unsafeの目的でstackallocと考えるものを変更する必要がありますか?

stackalloc ルール

LDM は、上記で定義したstackalloc規則と、他のポインター関連の変更のようなオプトインに関係なく適用される必要があるかどうかを確認する必要があります。

AllowUnsafeBlocks

AllowUnsafeBlocksの意味は現在変更されていません。trueキーワードまたはunsafeを使用できるようにするには、SkipLocalsInitAttributeに設定する必要があります。 BCL はその属性を SkipLocalsInitAttribute としてマークできるため、更新された規則の下でに必要とすべきではありませんか? safeキーワードにも必要ですか? unsafeブロックとunsafeメンバー宣言またはその他の組み合わせの両方に必要ですか?

(回答) unsafe

より包括的なunsafe機能を備えた他の言語では、ユーザーの人間工学を改善し、作成者がunsafeを使用する場所をより正確に制限できる表現としてunsafeが追加されています。 これは C# で必要なものですか? 安全性を直接処理する unsafe メンバーへのインライン呼び出しを考えてみましょう。現時点では、作成者はステートメント全体を unsafe ブロックでラップし、 unsafe コンテキストのスコープを拡張するか、内部関数呼び出しを中間変数に分割する必要があります。

extern int Add(int i1, int i2); // Some fancy extern addition function

// Code I want to write:
Console.WriteLine(unsafe(Add(1, 2)));

// Code I have to write option 1, unsafe context unnecessary includes the WriteLine call
unsafe
{
    Console.WriteLine(Add(1, 2));
}

// Code I have to write option 2, very verbose and harder to read:
int result;
unsafe
{
    result = Add(1, 2);
}
Console.WriteLine(result);

回答: はい詳細な設計セクションを参照してください。

その他の unsafe コンテキストとリラクゼーション

反復子と非同期メソッドの unsafe パラメーターとポインター パラメーターに関するより多くの制限を緩和する必要がありますか? ユーザーがawait UnsafeMethod()に書き換える必要があるため、特にTask t; unsafe { t = UnsafeMethod(); } await t;を許可すると便利です。 詳細については、 反復子/async の ref/unsafe を参照してください。

また、安全なコンテキストで &UnsafeMethod を許可する必要がありますか? 現在、提案が立っているとおり、メソッドがunsafeとしてマークされている場合は、unsafeコンテキストが必要です。 しかし、逆参照/呼び出し時に unsafe コンテキストを必要とするアドレスを取得しているだけなので、安全なコンテキストでそれ自体のアドレスを許可することができます。

LangVersion の安全でないリラクゼーション

LangVersion で安全でないコンテキストの緩和を 無条件にする必要がありますか?

  • LDM 2026-04-06: メモリ安全規則のバージョンでは条件付きではありません
  • TODO: LangVersion はどうですか?

(回答) unsafe 型の場合

unsafe型の字句スコープ全体を自動的にunsafeコンテキストにすることは検討できず、意味がないため、型のunsafeに対して警告を表示することはできません。

  • LDM 2025-11-12: 型の unsafe には意味がありません
  • LDM 2026-05-13: エラーになります (フィードバックに基づいて再検討できます)

回答: 型の unsafe は、更新されたルールの下でエラー (フィードバックに基づいて再検討できます) です。

unsafe アクセサーの場合

他の既存の修飾子に沿って、プロパティ アクセサーに対する unsafe を新しく許可しますが、イベント アクセサーでは許可しません。 つまり、プロパティの unsafe はアクセサーの unsafe のショートカットにすぎません。 ただし、同時に、 partialでは unsafe 修飾子が一致する必要があります。

partial class C
{
    unsafe partial int P { get; set; } // effectively both `get` and `set` are `unsafe` here
    unsafe partial int P { unsafe get => 0; set { } } // still an error: `unsafe` on `get` doesn't match
}

// similar to this pre-existing behavior:
unsafe partial class D
{
    unsafe partial void M();
}
unsafe partial class D
{
    partial void M() { } // error about missing `unsafe`
}

おそらく、 readonlyと同様に動作する必要があります。つまり、プロパティとそのアクセサーの両方で同時に unsafe を禁止します。

partial struct S
{
    readonly partial int P { get; set; }

    // error: Both partial member declarations must be readonly or neither may be readonly
    partial int P { readonly get => 0; set { } }

    // error: Cannot specify 'readonly' modifiers on both property or indexer 'S.P2' and its accessor. Remove one of them.
    readonly int P2 { readonly get => 0; set { } }
}

(回答)エッジ ケース のシナリオで 、不要な安全でない エラーの抑制を許可する

次のシナリオで 、必要な安全でない エラーを抑制するにはどうすればよいですか?

class A : System.Attribute
{
    unsafe public A() { } // declaring requires-unsafe constructor
}

class C
{
    [A] public void M() { } // error: applying requires-unsafe `A..ctor`
}

class B : A
{
    public B() { } // error: calling requires-unsafe `A..ctor` (implicit `: base()`)
}

class X<T> where T : new();
class D
{
    public void M(X<A> x) { } // error: using `X` which uses requires-unsafe `A..ctor`
}

安全でない必要があるエラーを抑制するには、それらのメンバーのシグネチャにunsafe コンテキストを何らかの方法で導入する必要があります。 ただし、 unsafe キーワードでは、 unsafe コンテキストは 導入されなくなりましたunsafeシグネチャにunsafeコンテキストを導入することもできますが、基底の必須アンセーフコンストラクターを呼び出したい場合は、コンストラクターを強制的に必須セーフにする必要があります。 これらのまれなエッジ ケースにヒットした場合、ユーザーがインプレースを抑制できる署名の警告で 、必要な安全でない 使用を行うことができます。

型にunsafeしてもコンテキストが導入unsafe、型にも同様の問題があります。

class A : Attribute
{
    unsafe public A() { } // declaring requires-unsafe constructor
}

[A] class C; // error: applying requires-unsafe `A..ctor`

class B() : A(); // error: calling requires-unsafe `A..ctor`

class X<T> where T : new();
class D : X<A>; // error: inheriting from `X` which uses requires-unsafe `A..ctor`
  • LDM 2026-05-13:
    • 属性アプリケーションのような実行可能でないコードは、(フィードバックが聞こえるまで) 抑制不可能なエラーのままである必要があります
    • new()などの他のエッジ ケースも、フィードバックが聞こえるまでエラーのままになる可能性があります
    • パラメーターなしの 必須の安全でない コンストラクターの宣言を禁止しない
    • unsafeコンストラクターの初期化子のみが、requires-unsafebase または this コンストラクターを呼び出すことができます

params コレクション

class C
{
    unsafe public C() { } // declaring requires-unsafe constructor
}

class B
{
    public void M(params C c) { }
}

安全でないunsafeコレクションは呼び出しサイトで構築されているため、このようなメソッドを呼び出すとparamsコンテキストが必要になるため、この宣言は単に許可できます。 そのため、宣言は実質的に 安全でないためunsafe 注釈を必要とするか、少なくともその事実について警告することができます。 paramsコレクションケースは、ここで他の同様の機能 (ObsoleteUnmanagedCallersOnly) がどのように動作するかに沿ったエラーですが、実装のバグである可能性があることに注意してください。

よく知られているメンバー

実装の簡略化とサニティのために、コンパイラは、すべての既知のメンバー ( Array.Length など) が安全であると見なすことができる (つまり、 安全でない) と見なすことができるとコンパイラが自由に想定できることを提案します。

Xml ドキュメント

呼び出し元に義務を渡すことは、その義務が何であるかを明確にする責任があります。 xml ドキュメントで既に表すことができるものを超えて、これを形式化する必要がありますか?

unsafeマークされたメンバーには、コードが正しいことを確認するために呼び出し元が何を行う必要があるかを示すコメントが必要です。 ドキュメントを見やすく区別しやすくするために、新しい XML ドキュメント タグが役立ちます: <safety />。 すべての事前/事後条件が <safety> ブロックに配置されることが予想されます。

unsafeブロックの推論を文書化するには、// SAFETY と同様にコメントを使用することをお勧めします。 また、コンパイラでこれらをチェックする必要がありますか (たとえば、既定で何らかの警告が表示されます)、またはアナライザーに任せる必要がありますか?

より意味のない unsafe 警告

より多くの宣言が無意味な unsafe 警告またはエラーを生成する必要がありますか? たとえば、空の本体 (または extern) を持つメソッドなどです。ただし、不要な unsafe 用の IDE アナライザーは既にあります。

静的コンストラクターと同様に、 [ModuleInitializer] unsafe void M() { } エラーにする必要がありますか?

(回答) unsafe フィールド

現在、フィールドの unsafe に関する提案は行われません。 ただし、 unsafe としてマークされたフィールドに対する読み取りまたは書き込みが unsafe コンテキストに含まれるように、追加が必要になる場合があります。 これにより、次のようなコードに関する懸念事項に注釈を付けることができます。

class SafeWrapper
{
    internal byte* _p;

    public void DoStuff()
    {
        unsafe
        {
            // ... validate that the object state is good ...
            // ... perform operation with _p .... 
        }
    }
}

// Elsewhere in safe code:
void M(SafeWrapper w)
{
     w._p = stackalloc byte[10];
}

また、自動プロパティのバッキング フィールドを unsafeとしてマークする必要がありますか?

メンバーの 決定 と一貫性を保つには、フィールドに unsafe しても unsafe コンテキストを導入しないことをお勧めします。 フィールドの初期化子で 必須の安全でない 操作が使用されている場合、ユーザーはいつでもそれらをメソッドにカプセル化できます。また、 unsafe 式を導入することもできます。

  • LDM 2026-05-13: フィールドは、を介して unsafe とマークできます。初期化子はunsafeコンテキストにありません。

(回答)明示的なレイアウト

[StructLayout(Explicit)]または[ExtendedLayout]としてマークされた構造体のフィールドは、unsafeとしてマークする必要がありますか?

推奨事項: はい。

  • LDM 2026-05-13: はい、unsafeの場合と同様に、safeまたはexternが必要です。

明示的なレイアウトとバッキング フィールド

コンパイラが Explicit/Extended 型の自動プロパティまたはフィールドに似たイベントのバッキング フィールドを合成する場合は、代わりにプロパティ/イベントに safe/unsafe する必要がありますか? それ以外の場合、ユーザーはこれらの自動宣言を手動フィールドとラッパー メンバー宣言に展開することを強制されます。 バッキング フィールドを取得するプライマリ コンストラクター パラメーターはどうでしょうか。 パラメーター宣言では、現在、 safe 修飾子と unsafe 修飾子の両方が許可されていません。

[Out] および [SkipLocalsInit]

たとえば、VB では [Out] パラメーターが初期化される保証がないため、 [SkipLocalsInit]と組み合わせて、このようなパラメーターの呼び出しは C# で unsafe と見なされる可能性があります。 一方、呼び出し先の問題は、 [Out] コントラクトを支持していないと感じます (同様に、他の多くの点で安全ではない可能性があります)。

これらのケースを unsafeする必要があると判断した場合は、 オプトインした ことがわかっているメソッドを除外できます (現在は、 [Out] の正しい使用を保証する C# からのメソッドですが、他の言語が新しいルールを実装する場合は、そのことを保証する必要があります)。

初期化されていない変数のアドレスを取得する

現在、確実に割り当てられていない変数のアドレスを取得すると、その変数が確実に割り当てられていると見なされ、初期化されていないメンバーが公開されます。 これを解決するには、次の 2 つのオプションがあります。

  1. アドレス演算子を使用できるようにする前に、変数を確実に割り当てる必要があります。
  2. 初期化されていない変数のアドレスを安全でなくします。

例:

static void SkipInit<T>(out T value)  
{
    // value is considered definitely assigned after the address-of
    fixed (void* ptr = &value);
}
int i;
// i is considered definitely assigned after the address-of
_ = &i;
// Incrementing whatever was on the stack
i++;

MemorySafetyRulesAttribute の値

"enabled"/"updated" メモリセーフルールのバージョンは何ですか? 2? 15? 11? 「SDK の安全でない導入」と「より段階的なオプトイン」も参照してください。

(回答)より段階的なオプトイン?

現在、オプトインでは、独自のコードでの安全でないルールの適用と、注釈が意図的であるという (アセンブリ レベルの属性を介して) コンシューマーに公開されるシグナルという 2 つのことが一度に提供されています。 アセンブリに完全に注釈を付けたことを公開する準備ができていない状態で、注釈を付けて強制診断の取得を開始したいユーザーが存在する場合があります。 安全でない診断が警告として表示され、完全なオプトインによってエラーに昇格する "中間" オプトイン レベルが必要ですか?

  • LDM 2026-04-29: はい、プレビューではブロックされていません

(回答)きめ細かいオプトイン

null 許容参照型用に構築されたものに似た、きめ細かいリージョンベースのオプトイン メカニズムを提供します。ユーザーはディレクティブを使用して、ソース コードの特定のリージョンに対して機能を有効にできますか? コード領域のオプトイン/アウトも参照してください。

(回答) extern 暗黙的に安全でない

これは現在、明示的なRequiresUnsafeAttribute キーワードなしでunsafeが合成される唯一の場所です。 この外れ値は大丈夫ですか?

また、CoreLib では、多くの extern メソッド (FCalls) が現在安全として公開されています。 extern メソッドを暗黙的に安全でないメソッドとして扱うには、暗黙的に安全でない extern メソッドを安全なラッパーでラップする必要があります。 ランタイムの実装の詳細により、追加ラッパーの追加が困難な状況が発生する可能性があります。

  • LDM 2026-04-01: extern メンバーは、安全または安全でないと明示的にマークする必要があります
  • LDM 2026-04-06: 同じ決定が繰り返されました
  • LDM 2026-04-13: safe キーワードの使用に関する一時的な決定
  • LDM 2026-05-13: safe キーワードを使用します (ただし、再検討は可能です)

回答: extern メンバーは、 unsafe または safe のいずれかにマークする必要があります (新しいキーワードを追加しますが、フィードバックに基づいて機能が出荷される前に再検討できます)。

extern以外のメンバーのsafeを許可する (LibraryImport)

この質問を検討するのは今回が初めてではありません。 作業部会は当初、 extern メンバーと LibraryImport メンバーについて話し合いました。 LDM は、署名に unsafe ブロックまたはポインターを持つメンバーが明示的な safe マーカーを運ぶ必要があるかどうかを検討し、分析可能なメソッド本体にこの儀式は必要ないことを 決定 しました。 extern 境界とは異なり、実装を検査して、安全義務を放棄するかどうかを判断できます。 そのため、 safe は現在、明示的なレイアウトのメンバーやフィールドなど、明示的な安全性の選択が必要な場合にのみ許可 extern

その後、ライブラリ チームは、LibraryImportソース生成からの新しいデータを提供し、質問の再検討を保証しています。 LibraryImport は P/Invoke の推奨される最新の形式ですが、生成された部分実装が extern かどうかは実装の詳細です。 blittable シグネチャの場合、ジェネレーターは直接 extern 実装を出力できます (例は簡略化されています)。

// User code
[LibraryImport("kernel32.dll")]
static partial int Blit(int x);

// Generated code
[DllImport("kernel32.dll", EntryPoint = "Blit", ExactSpelling = true)]
static extern partial int Blit(int x);

生成された extern 宣言は、 safe または unsafeとしてマークする必要があり、部分的な宣言は、その安全性修飾子に同意する必要があります。 したがって、ユーザーが作成した宣言には同じ修飾子が必要です。

マーシャリングを必要とする署名の場合、ジェネレーターは代わりにプライベート externの周囲にマネージド ラッパーを出力します。

// User code
[LibraryImport("kernel32.dll")]
static partial int NotBlit(ref int x);

// Generated code
static partial int NotBlit(ref int x)
{
    fixed (int* xNative = &x)
    {
        return __PInvoke(xNative);
    }

    [DllImport("kernel32.dll", EntryPoint = "NotBlit", ExactSpelling = true)]
    static extern unsafe int __PInvoke(int* xNative);
}

ここでは、ユーザー向けの部分メソッドは externされていないため、 safe は許可されません。 したがって、ユーザーが使用できる構文は、ジェネレーターが選択する実装形状によって異なります。ただし、その選択は LibraryImport コントラクトの一部ではなく、ジェネレーターの進化に合わせて変更される可能性があります。 ラッパーを常に生成すると構文は一貫性がありますが、直接出力できるシグネチャを悲観し、 LibraryImportDllImportのユーザー エクスペリエンスの違いを生み出します。

メンバー safe 必須でない場合でも、 unsafe がメンバーに 安全でないマークを付けることができるすべての宣言で許可する必要がありますか? または、ソース ジェネレーターによって実装される部分メンバーに対して、言語によってより狭い規則が提供される必要がありますか?

ワーキング グループの推奨事項: 宣言に必要な安全でない宣言をマークできる任意の場所unsafe宣言修飾子としてsafeを許可します。 それが必要でない場合、 safe は no-opです。 非extern ラッパーを生成するLibraryImportなど、言語に明示的な修飾子を要求する必要があるシナリオでは、safeまたはunsafeの存在を強制するアナライザーが必要になります。

(回答) unsafe メンバーのコンテキストの既定値

unsafe メソッドの本体全体を自動的にunsafeコンテキストにすることは検討できませんでした。 Rust は RFC 2585 でこれを行い、 unsafe ブロックの範囲を、 unsafe が実際に使用される場所に減らすのに役立つという動機を持っています。 C# でも同様の動機を持つ警告またはエラーとして同じことを行うことができます。

  • LDM 2026-04-22: はい、署名の unsafe は本文を作成しません unsafe

(回答) new() 制約

new()をサポートしますか (現在、コンパイラではObsoleteなどの他の機能をサポートしていないようです)

M<C>(); // should be an error outside `unsafe` context since `M` calls the requires-unsafe `C..ctor`?

void M<T>() where T : new()
{
    _ = new T();
}

class C
{
    unsafe public C() { }
}
  • LDM 2026-05-13: 必須の安全でない パラメーターなしのコンストラクターを持つ型は、 new() 制約を満たしてはなりません

new()制約とusing

エイリアスと静的な using でどのように動作する必要がありますか?

  • using宣言でエラーが発生した場合は、既にサポートされている unsafe キーワードを使用して抑制できます。
  • エイリアスや静的な使用なしで直接使用する場合と同様に、使用サイトで通常エラーにする必要がありますか?

Note

2 番目のケースでは、エイリアスと静的な using を使用するための "意味のない unsafe" 警告を追加する必要があります。

class C
{
    unsafe public C() { }
}

class D<T> where T : new()
{
    public static void M() { _ = new T(); }
}
using X = D<C>;
using unsafe X = D<C>;

X.M();
using static D<C>;
using static unsafe D<C>;

M();

他の制約は、現在の以前のオプションと同様に動作します。

using X = D<C>; // error here

_ = new X(); // ok
_ = new D<C>(); // error here

class C
{
    public C(int x) { }
}

class D<T> where T : new();

より多くのコンストラクトを unsafeする必要がありますか?

  • dynamic (おそらく、リフレクション API に対して BCL が決定した内容と一致する必要があります)

(回答)スキューを行う方法

質問テキスト

最初の提案は、主に私たちがどれだけ積極的になりたいかのlitmusテストとして、最大の破壊的アプローチです。 これは、コードのセクションをオプトイン/アウトする機能を提案せず、メソッドの unsafe の意味を変更し、型での unsafe の使用を禁止し、警告ではなくエラーを使用し、一般的にコンパイラがアップグレードされた時点で移行を一度に強制的に実行します (その後、依存関係が更新され、既に使用されていたメンバーに unsafe が追加される可能性があります)。 ただし、このような変更を行う豊富な経験があり、分割のサイズを範囲指定し、増分導入を許可することができます。 これらのオプションについては、以下で説明します。

コード領域のオプトイン/オプトアウト

C# が未記述コードの "基本" ケースを再定義したのは、今回が初めてではありません。 C# 8.0 では、null 許容参照型機能が導入されました。これは、多くの点で、 unsafe 機能がどのように整形されているかのブループリントと見なすことができます。 同様の目標 (既定の C# の解釈方法を再定義して数十億ドルのバグを防ぐ) と同様の一般的な機能セット (状態を伝達し、バグを回避するために型に新しい情報を追加する) がありました。 また、非常に破壊的であり、コードベースで機能を時間の経過と同時に採用できるようにするために、強力なオプトインとオプトアウトの機能が必要でした。 その機能は、"null 許容参照型コンテキスト" です。 これは構文スコープであり、コード内の特定の領域について、未通知の型参照を解釈する方法と、ユーザーに与える警告の種類の両方をコンパイラに通知します。 これを unsafe のモデルとして使用したり、"安全規則コンテキスト" を追加したり、これらの新しい規則が適用されているかどうかを制御したりすることもできます。

新しい unsafe 機能の利点の 1 つは、それらがあまり普及しなくなることです。 上位のライブラリには適切な数の unsafe 呼び出しがありますが、 unsafe を使用する上位ライブラリの割合に関する推測は、"これまでに記述された C# コードのすべての行" よりもはるかに低くなっています。 うまくいけば、これは、オプトイン/アウトを行う機能が必要になる可能性がある一方で、専用のプリプロセッサスイッチなどを使用して、nullableのように複雑なメカニズムを必要としないことを意味します。

警告とエラー

現在、この提案では、メモリの安全性要件は、エラーではなく警告によって適用されていることを示しています。 これは、null 許容機能を使用した経験を基にしています。警告により、コード ベースは新しい機能を段階的に導入でき、大量のコードを一度に変換する必要はありません。 安全でない警告にも同様のプロセスが必要になると予想されます。多くのコードベースでは、新しいルールをグローバルに有効にして、生活に移ることができます。 しかし、新しいルールの採用に最も関心があるコードベースには、注釈を付ける大量のコードが含まれると想定しています。また、エラーの壁を見てすぐにあきらめるのではなく、機能を進めることができるようにしたいと考えています。 要件の警告を行うことで、これらのコードベースでは、必要に応じてファイルごとまたはメソッドごとの警告を修正し、他のすべての場所で警告を無効にできます。

メソッドシグネチャの中断

ここでは、メソッドのキーワードとして unsafe 、セマンティックに影響を与えずに構文的にスコープ設定されたものから、構文的にスコープが設定されていないものに移行することを提案しています。 この中断を制限するには、メソッドまたはメンバーの呼び出し元が unsafe コンテキストに存在する必要がある場合 (修飾子として callerunsafe など) に新しいキーワードを導入します。

ソース ジェネレーターの既定値

null 許容の場合、ジェネレーターの作成者は、プロジェクト全体が既定で機能にオプトインしたかどうかに関係なく、明示的に null 許容にオプトインするように強制します。これにより、ユーザーが null 許容をオンにしてエラーとして警告することでジェネレーターの出力が壊れないようにします。 ソース ジェネレーターでも同じ操作を行う必要がありますか?

まとめ

LDM 2025-11-05 で回答しました。 新しいルールが有効になっているときにメモリの安全性に関する問題のエラーを報告します。ソース ジェネレーターの例外は発生しません。

(回答)エラーまたは警告

(回答)ソース ジェネレーターアフォーダンス

(回答)safe ブロックまたはポインターを持つメンバーには、unsafe Maker が必要ですか?

(回答)非オプトイン呼び出し元の互換性モードも?

回答: シグネチャにポインターを持つ非オプトイン メンバーは、オプトイン呼び出し元に 対して安全ではないと 見なされます。

(回答)互換性モードを拡張しますか?

nintSystem.IntPtrもポインターと考える必要がありますか? 非オプトイン呼び出し元からの extern/DllImport、必須セーフではないと 考える必要がありますか? オプトイン アセンブリが非オプトイン アセンブリを参照する場合、一括警告が表示される必要がありますか?

  • LDM 2026-04-29: 拡張機能がありません (アナライザーは、いくつかのより少ない安全でない信号をカバーする可能性があります)