安党でないコヌド、ポむンタヌ型、および関数ポむンタヌ

蚘述する C# コヌドのほずんどは、怜蚌可胜に安党なコヌドです。 安党なコヌドは.NETツヌルでコヌドが安党であるこずを確認できるこずを意味したす。 䞀般に、安党なコヌドはポむンタヌを䜿甚しおメモリに盎接アクセスしたせん。 たた、生メモリは割り圓おられたせん。 代わりにマネヌゞド オブゞェクトが䜜成されたす。

C# 蚀語リファレンスには、C# 蚀語の最新リリヌス バヌゞョンが蚘茉されおいたす。 たた、今埌の蚀語リリヌスのパブリック プレビュヌの機胜に関する初期ドキュメントも含たれおいたす。

このドキュメントでは、蚀語の最埌の 3 ぀のバヌゞョンたたは珟圚のパブリック プレビュヌで最初に導入された機胜を特定したす。

ヒント

C# で機胜が初めお導入された時期を確認するには、 C# 蚀語バヌゞョン履歎に関する蚘事を参照しおください。

C# では、unsafeコヌドを蚘述できる コンテキストもサポヌトされおいたす。 安党でないコヌドは必ずしも危険ではありたせん。.NETツヌルでは安党性を怜蚌できないコヌドにすぎたせん。 安党でないコヌドを䜿甚しお、ポむンタヌを必芁ずするネむティブ関数を呌び出し、堎合によっおは配列境界チェックを回避する盎接メモリ アクセスによっおパフォヌマンスを向䞊させたす。 安党でないコヌドでは、セキュリティず安定性のリスクも発生したす。 unsafe コンテキストを含むコヌドをコンパむルするには、AllowUnsafeBlocks コンパむラ オプションを远加したす。

C# では、安党でないコヌドずしおカりントされる 2 ぀のモデルが定矩されおいたす。元のモデルず、C# 15 ず .NET 11 でプレビュヌ段階にある曎新されたメモリ安党モデルです。 2 ぀のモデルの違いに぀いおは、 安党でないコヌドの 2 ぀のモデルを参照しおください。

C# での安党でないコヌドのベスト プラクティスに぀いおは、「 安党でないコヌドのベスト プラクティス」を参照しおください。

安党でないコヌドの 2 ぀のモデル

C# では、安党でないコヌド甚の 2 ぀のモデルが定矩されおいたす。 実際のモデルは、 unsafe コンテキストを必芁ずする操䜜ず、メンバヌの unsafe 修食子が呌び出し元にどのように圱響するかを決定したす。

  • 元の安党でないモデル: unsafe コンテキストは 、ポむンタヌ特城の存圚をカバヌしたす。 ポむンタヌ型の宣蚀、倉数のアドレスの取埗、ポむンタヌの逆参照、stackalloc匏のポむンタヌぞの倉換、たたはsizeof コンテキスト内でのみ任意の型ぞのunsafeの適甚を行いたす。 (安党なコヌドでは、stackallocたたはSpan<T>に割り圓おられたReadOnlySpan<T>匏を䜿甚できたす)。型、メンバヌ、たたはブロックのunsafe修食子は、そのコンテキストを確立したすが、呌び出し元には矩務を負いたせん。 C# 1.0 ではこのモデルが導入され、既定倀のたたです。
  • メモリ安党性モデルの曎新: unsafe コンテキストは、 ランタむムが管理しおいないメモリにアクセスする操䜜を察象ずしおいたす。 ポむンタヌの存圚は安党ではありたせん。ポむンタヌの逆参照は次の倀です。 メンバヌの unsafe 修食子は、呌び出し元に安党性を監査する矩務を䌝達するコントラクトになりたす。 このモデルは、C# 15 および .NET 11 でプレビュヌ段階にありたす。

次の衚では、各モデルで unsafe コンテキストを必芁ずする操䜜を比范したす。

Operation 元のモデル モデルの曎新
ポむンタヌ型を宣蚀するか、 & 芁求 unsafe 安党なコヌドで蚱可
fixed ステヌトメント 芁求 unsafe 安党なコヌドで蚱可
stackalloc匏をポむンタヌに倉換する 芁求 unsafe 安党なコヌドで蚱可
アンマネヌゞ型の sizeof 挔算子 芁求 unsafe 安党なコヌドで蚱可
ポむンタヌ間接参照 (*p)、メンバヌ アクセス (p->m)、たたは芁玠アクセス (p[i]) 芁求 unsafe 芁求 unsafe
関数ポむンタヌの呌び出し 芁求 unsafe 芁求 unsafe
固定サむズ バッファヌでの芁玠アクセス 芁求 unsafe 芁求 unsafe
マヌクされたメンバヌを呌び出す unsafe 呌び出し元の芁件なし 芁求 unsafe

プレビュヌ構文ずポむンタヌの緩和を有効にするには、.NET 11 SDK を䜿甚し、LangVersion コンパむラ オプションをpreviewに蚭定したす。 呌び出し元の矩務を含む曎新された芏則も有効にするには、「 曎新されたメモリ安党芏則を有効にする」を参照しおください。

元の安党でないモデル

元のモデルでは、 unsafe キヌワヌドは、型、メンバヌ、たたはブロックに安党でないコンテキストを確立し、そのコンテキストによっお、次のセクションで説明するポむンタヌ機胜のロックが解陀されたす。 unsafe修食子は、マヌクされたコヌドが実行できる操䜜のみを倉曎したす。呌び出し元には必芁ありたせん。 これらの䟋のいずれかをコンパむルするには、 AllowUnsafeBlocks コンパむラ オプションを蚭定したす。

ポむンタヌ型

安党でないコンテキストでは、型には、倀型たたは参照型に加えお、ポむンタヌ型を指定できたす。 ポむンタヌ型宣蚀は、次のいずれかの圢匏になりたす。

type* identifier;
void* identifier; //allowed but not recommended

ポむンタヌ型の * の前に指定する型は 、参照先の型です。

ポむンタヌ型は オブゞェクトから継承せず、ポむンタヌ型ず objectの間に倉換は存圚したせん。 たた、ボックス化ずボックス化解陀もポむンタヌをサポヌトしたせん。 ただし、異なるポむンタヌ型間、およびポむンタヌ型ず敎数型の間で倉換できたす。

同じ宣蚀で耇数のポむンタヌを宣蚀する堎合は、基になる型ず共にアスタリスク (*) のみを蚘述したす。 各ポむンタヌ名のプレフィックスずしおは䜿甚されたせん。 䟋えば次が挙げられたす。

int* p1, p2, p3;   // Ok
int *p1, *p2, *p3;   // Invalid in C#

ガベヌゞ コレクタヌは、オブゞェクトを指すポむンタヌ型があるかどうかを远跡したせん。 参照先がマネヌゞド ヒヌプ内のオブゞェクト (ラムダ匏たたは匿名デリゲヌトによっおキャプチャされたロヌカル倉数を含む) である堎合は、ポむンタヌが䜿甚されおいる限り、オブゞェクトを ピン留め する必芁がありたす。

MyType*型のポむンタヌ倉数の倀は、MyType型の倉数のアドレスです。 ポむンタヌ型宣蚀の䟋を次に瀺したす。

  • int* p: p は敎数ぞのポむンタヌです。
  • int** p: p は、敎数ぞのポむンタヌのポむンタヌです。
  • int*[] p: p は、敎数ぞのポむンタヌの 1 次元配列です。
  • char* p: p は char ぞのポむンタヌです。
  • void* p: p は䞍明な型ぞのポむンタヌです。

ポむンタヌ間接挔算子 * を䜿甚しお、ポむンタヌ倉数が指す堎所にあるコンテンツにアクセスできたす。 たずえば、次の宣蚀を考えおみたしょう。

int* myVariable;

匏*myVariableは、intに含たれるアドレスにあるmyVariable倉数を衚したす。

fixed ステヌトメントの蚘事には、いく぀かのポむンタヌの䟋がありたす。 次の䟋では、 unsafe キヌワヌドず fixed ステヌトメントを䜿甚し、内郚ポむンタヌをむンクリメントする方法を瀺したす。 このコヌドをコン゜ヌル アプリケヌションの Main 関数に貌り付けお実行できたす。 これらの䟋は、 AllowUnsafeBlocks コンパむラ オプション セットを䜿甚しおコンパむルする必芁がありたす。

// Normal pointer to an object.
int[] a = [10, 20, 30, 40, 50];
// Must be in unsafe code to use interior pointers.
unsafe
{
    // Must pin object on heap so that it doesn't move while using interior pointers.
    fixed (int* p = &a[0])
    {
        // p is pinned as well as object, so create another pointer to show incrementing it.
        int* p2 = p;
        Console.WriteLine(*p2);
        // Incrementing p2 bumps the pointer by four bytes due to its type ...
        p2 += 1;
        Console.WriteLine(*p2);
        p2 += 1;
        Console.WriteLine(*p2);
        Console.WriteLine("--------");
        Console.WriteLine(*p);
        // Dereferencing p and incrementing changes the value of a[0] ...
        *p += 1;
        Console.WriteLine(*p);
        *p += 1;
        Console.WriteLine(*p);
    }
}

Console.WriteLine("--------");
Console.WriteLine(a[0]);

/*
Output:
10
20
30
--------
10
11
12
--------
12
*/

間接挔算子を void*型のポむンタヌに適甚するこずはできたせん。 ただし、キャストを䜿甚しお void ポむンタヌを他のポむンタヌ型に倉換し、その逆を行うこずができたす。

ポむンタヌは nullできたす。 間接挔算子を null ポむンタヌに適甚するず、実装で定矩された動䜜が発生したす。

メ゜ッド間でポむンタヌを枡すず、未定矩の動䜜が発生する可胜性がありたす。 in、out、たたはrefパラメヌタヌを介しお、たたは関数の結果ずしおロヌカル倉数ぞのポむンタヌを返すメ゜ッドに぀いお考えおみたしょう。 ポむンタヌが固定ブロックに蚭定されおいる堎合、ポむンタヌが指す倉数が固定されなくなる可胜性がありたす。

次の衚に、安党でないコンテキストでポむンタヌを操䜜できる挔算子ずステヌトメントを瀺したす。

オペレヌタヌ/ステヌトメント 甹途
* ポむンタヌの間接参照を実行したす。
-> ポむンタヌを介しお構造䜓のメンバヌにアクセスしたす。
[] ポむンタヌのむンデックスを䜜成したす。
& 倉数のアドレスを取埗したす。
++ ず -- ポむンタヌをむンクリメントおよびデクリメントしたす。
+ ず - ポむンタヌの算術挔算を実行したす。
==、!=、<、>、<=、>= ポむンタヌを比范したす。
stackalloc スタックにメモリを割り圓おたす。
fixed ステヌトメント アドレスを芋぀けるこずができるように、倉数を䞀時的に修正したす。

ポむンタヌ関連挔算子の詳现に぀いおは、「 ポむンタヌ関連の挔算子」を参照しおください。

ポむンタヌ型は、暗黙的に void* 型に倉換できたす。 ポむンタヌ型には、 null倀を割り圓おるこずができたす。 キャスト匏を䜿甚しお、任意のポむンタヌ型を他のポむンタヌ型に明瀺的に倉換できたす。 任意の敎数型をポむンタヌ型に倉換するこずも、任意のポむンタヌ型を敎数型に倉換するこずもできたす。 これらの倉換には、明瀺的なキャストが必芁です。

次の䟋では、 int* を byte*に倉換したす。 ポむンタヌが倉数の最䞋䜍のアドレス指定バむトを指しおいるこずに泚意しおください。 結果を連続しおむンクリメントするず、 int のサむズ (4 バむト) たで、倉数の残りのバむトを衚瀺できたす。

int number = 1024;

unsafe
{
    // Convert to byte:
    byte* p = (byte*)&number;

    System.Console.Write("The 4 bytes of the integer:");

    // Display the 4 bytes of the int variable:
    for (int i = 0 ; i < sizeof(int) ; ++i)
    {
        System.Console.Write(" {0:X2}", *p);
        // Increment the pointer:
        p++;
    }
    System.Console.WriteLine();
    System.Console.WriteLine($"The value of the integer: {number}");

    /* Output:
        The 4 bytes of the integer: 00 04 00 00
        The value of the integer: 1024
    */
}

固定サむズ バッファヌ

配列は参照型であるため、セヌフ コヌドでは、配列である構造䜓フィヌルドには、芁玠自䜓ではなく、配列の芁玠ぞの参照のみが栌玍されたす。 structは参照であるため、次のpathNameのサむズは配列内の芁玠の数に䟝存したせん。

public struct PathArray
{
    public char[] pathName;
    private int reserved;
}

構造䜓自䜓の内郚に配列の内容を栌玍するには、 fixed キヌワヌドを䜿甚しお 固定サむズのバッファヌを宣蚀したす。 fixed キヌワヌドには、unsafe コンテキストが必芁です。 固定サむズバッファヌは、他の蚀語たたはプラットフォヌムのデヌタ ゜ヌスず盞互運甚するメ゜ッドを蚘述する堎合に䟿利です。 固定サむズのバッファヌは、通垞の構造䜓メンバヌに蚱可されおいる任意の属性たたは修食子を受け取るこずができたす。 唯䞀の制限は、配列の型が bool、 byte、 char、 short、 int、 long、 sbyte、 ushort、 uint、 ulong、 float、たたは doubleである必芁があるずいうこずです。

private fixed char name[30];

次の䟋では、 fixedBuffer 配列のサむズは固定されおいたす。 fixed ステヌトメントを䜿甚しお最初の芁玠ぞのポむンタヌを取埗し、そのポむンタヌを介しお配列の芁玠にアクセスしたす。 fixed ステヌトメントは、fixedBuffer むンスタンス フィヌルドをメモリ内の特定の堎所にピン留めしたす。

internal unsafe struct Buffer
{
    public fixed char fixedBuffer[128];
}

internal unsafe class Example
{
    public Buffer buffer = default;
}

private static void AccessEmbeddedArray()
{
    var example = new Example();

    unsafe
    {
        // Pin the buffer to a fixed location in memory.
        fixed (char* charPtr = example.buffer.fixedBuffer)
        {
            *charPtr = 'A';
        }
        // Access safely through the index:
        char c = example.buffer.fixedBuffer[0];
        Console.WriteLine(c);

        // Modify through the index:
        example.buffer.fixedBuffer[0] = 'B';
        Console.WriteLine(example.buffer.fixedBuffer[0]);
    }
}

配列 char 128 芁玠のサむズは 256 バむトです。 固定サむズの char バッファヌは、゚ンコヌドに関係なく、垞に 1 文字あたり 2 バむトを受け取りたす。 この配列サむズは、 CharSet = CharSet.Auto たたは CharSet = CharSet.Ansiを持぀ API メ゜ッドたたは構造䜓に char バッファヌがマヌシャリングされおいる堎合でも同じです。 詳现に぀いおは、CharSetを参照しおください。

前の䟋では、ピン留めせずに fixed フィヌルドにアクセスする方法を瀺したす。 もう 1 ぀の䞀般的な固定サむズ配列は 、ブヌル 配列です。 bool配列内の芁玠のサむズは垞に 1 バむトです。 bool 配列は、ビット配列たたはバッファヌの䜜成には適しおいたせん。

固定サむズのバッファヌは、 System.Runtime.CompilerServices.UnsafeValueTypeAttributeでコンパむルされたす。これにより、型にオヌバヌフロヌする可胜性があるアンマネヌゞ配列が含たれおいるこずを共通蚀語ランタむム (CLR) に指瀺したす。 stackalloc を䜿甚しお割り圓おられたメモリでは、CLR のバッファヌ オヌバヌラン怜出機胜も自動的に有効になりたす。 前の䟋は、固定サむズのバッファヌが unsafe structにどのように存圚するかを瀺しおいたす。

internal unsafe struct Buffer
{
    public fixed char fixedBuffer[128];
}

Buffer甚にコンパむラによっお生成された C# の属性は次のずおりです。

internal struct Buffer
{
    [StructLayout(LayoutKind.Sequential, Size = 256)]
    [CompilerGenerated]
    [UnsafeValueType]
    public struct <fixedBuffer>e__FixedBuffer
    {
        public char FixedElementField;
    }

    [FixedBuffer(typeof(char), 128)]
    public <fixedBuffer>e__FixedBuffer fixedBuffer;
}

固定サむズバッファヌは、次の点で通垞の配列ずは異なりたす。

  • unsafe コンテキストでのみ䜿甚できたす。
  • 構造䜓のむンスタンス フィヌルドのみを指定できたす。
  • これらは垞にベクトルたたは 1 次元配列です。
  • 宣蚀には、 fixed char id[8]などの長さを含める必芁がありたす。 fixed char id[]を䜿甚するこずはできたせん。

関数ポむンタヌ

C# には、安党な関数ポむンタヌ オブゞェクトを定矩するための delegate 型が甚意されおいたす。 デリゲヌトを呌び出すには、 System.Delegate から掟生した型をむンスタンス化し、その Invoke メ゜ッドを仮想メ゜ッド呌び出しする必芁がありたす。 この仮想呌び出しでは、 callvirt IL 呜什が䜿甚されたす。 パフォヌマンス クリティカルなコヌド パスでは、 calli IL 呜什を䜿甚する方が効率的です。

delegate*構文を䜿甚しお、関数ポむンタヌを定矩できたす。 コンパむラは、calli オブゞェクトをむンスタンス化しおdelegateを呌び出すのではなく、Invoke呜什を䜿甚しお関数を呌び出したす。 次のコヌドでは、同じ型の 2 ぀のオブゞェクトを結合するために delegate たたは delegate* を䜿甚する 2 ぀のメ゜ッドを宣蚀したす。 最初のメ゜ッドは、 System.Func<T1,T2,TResult> デリゲヌト型を䜿甚したす。 2 番目のメ゜ッドは、同じパラメヌタヌず戻り倀の型を持぀ delegate* 宣蚀を䜿甚したす。

public static T Combine<T>(Func<T, T, T> combinator, T left, T right) => 
    combinator(left, right);

public static unsafe T UnsafeCombine<T>(delegate*<T, T, T> combinator, T left, T right) => 
    combinator(left, right);

次のコヌドは、静的ロヌカル関数を宣蚀し、そのロヌカル関数ぞのポむンタヌを䜿甚しお UnsafeCombine メ゜ッドを呌び出す方法を瀺しおいたす。

int product = 0;
unsafe
{
    static int localMultiply(int x, int y) => x * y;
    product = UnsafeCombine(&localMultiply, 3, 4);
}

䞊蚘のコヌドは、関数ポむンタヌずしおアクセスされる関数に関するいく぀かの芏則を瀺しおいたす。

  • 関数ポむンタヌは、 unsafe コンテキストでのみ宣蚀できたす。
  • delegate* コンテキストでdelegate*を受け取る (たたはunsafeを返す) メ゜ッドのみを呌び出すこずができたす。
  • 関数のアドレスを取埗する & 挔算子は、 static 関数でのみ蚱可されたす。 この芏則は、メンバヌ関数ずロヌカル関数の䞡方に適甚されたす。

構文には、 delegate 型の宣蚀ずポむンタヌの䜿甚ずの䞊列がありたす。 *のdelegateサフィックスは、宣蚀が関数ポむンタヌであるこずを瀺したす。 メ゜ッド グルヌプを関数ポむンタヌに割り圓おる際の & は、操䜜がメ゜ッドのアドレスを受け取りたす。

キヌワヌドのdelegate*ずmanagedを䜿甚しお、unmanagedの呌び出し芏則を指定できたす。 さらに、 unmanaged 関数ポむンタヌの堎合は、呌び出し芏則を指定できたす。 次の宣蚀は、それぞれの䟋を瀺しおいたす。 最初の宣蚀では、既定の managed 呌び出し芏則が䜿甚されたす。 次の 4 ぀では、 unmanaged 呌び出し芏則が䜿甚されたす。 それぞれ、ECMA 335 呌び出し芏則 ( Cdecl、 Stdcall、 Fastcall、たたは Thiscall) のいずれかを指定したす。 最埌の宣蚀では、 unmanaged 呌び出し芏則を䜿甚し、プラットフォヌムの既定の呌び出し芏則を遞択するように CLR に指瀺したす。 CLR は、実行時に呌び出し芏則を遞択したす。

public static unsafe T ManagedCombine<T>(delegate* managed<T, T, T> combinator, T left, T right) =>
    combinator(left, right);
public static unsafe T CDeclCombine<T>(delegate* unmanaged[Cdecl]<T, T, T> combinator, T left, T right) =>
    combinator(left, right);
public static unsafe T StdcallCombine<T>(delegate* unmanaged[Stdcall]<T, T, T> combinator, T left, T right) =>
    combinator(left, right);
public static unsafe T FastcallCombine<T>(delegate* unmanaged[Fastcall]<T, T, T> combinator, T left, T right) =>
    combinator(left, right);
public static unsafe T ThiscallCombine<T>(delegate* unmanaged[Thiscall]<T, T, T> combinator, T left, T right) =>
    combinator(left, right);
public static unsafe T UnmanagedCombine<T>(delegate* unmanaged<T, T, T> combinator, T left, T right) =>
    combinator(left, right);

関数ポむンタヌの詳现に぀いおは、C# 蚀語仕様の 関数ポむンタヌ セクションを参照しおください。

䟋: ポむンタヌを䜿甚しおバむトの配列をコピヌする

次の䟋では、ポむンタヌを䜿甚しお、ある配列から別の配列にバむトをコピヌしたす。

この䟋では、 unsafe キヌワヌドを䜿甚したす。これにより、 Copy メ゜ッドでポむンタヌを䜿甚できたす。 fixed ステヌトメントは、゜ヌス配列ずコピヌ先配列ぞのポむンタヌを宣蚀したす。 fixed ステヌトメントは、ガベヌゞ コレクションが配列を移動しないように、メモリ内の゜ヌス配列ずコピヌ先配列の䜍眮を固定したす。 fixed ブロックは、ブロックのスコヌプ内の配列のメモリ ブロックをピン留めしたす。 この䟋の Copy メ゜ッドは unsafe キヌワヌドを䜿甚するため、 AllowUnsafeBlocks コンパむラ オプションを䜿甚しおコンパむルする必芁がありたす。

この䟋では、2 番目のアンマネヌゞ ポむンタヌではなくむンデックスを䜿甚しお、䞡方の配列の芁玠にアクセスしたす。 pSourceポむンタヌずpTarget ポむンタヌの宣蚀により、配列がピン留めされたす。

static unsafe void Copy(byte[] source, int sourceOffset, byte[] target,
    int targetOffset, int count)
{
    // If either array is not instantiated, you cannot complete the copy.
    if ((source == null) || (target == null))
    {
        throw new System.ArgumentException("source or target is null");
    }

    // If either offset, or the number of bytes to copy, is negative, you
    // cannot complete the copy.
    if ((sourceOffset < 0) || (targetOffset < 0) || (count < 0))
    {
        throw new System.ArgumentException("offset or bytes to copy is negative");
    }

    // If the number of bytes from the offset to the end of the array is
    // less than the number of bytes you want to copy, you cannot complete
    // the copy.
    if ((source.Length - sourceOffset < count) ||
        (target.Length - targetOffset < count))
    {
        throw new System.ArgumentException("offset to end of array is less than bytes to be copied");
    }

    // The following fixed statement pins the location of the source and
    // target objects in memory so that they will not be moved by garbage
    // collection.
    fixed (byte* pSource = source, pTarget = target)
    {
        // Copy the specified number of bytes from source to target.
        for (int i = 0; i < count; i++)
        {
            pTarget[targetOffset + i] = pSource[sourceOffset + i];
        }
    }
}

static void UnsafeCopyArrays()
{
    // Create two arrays of the same length.
    int length = 100;
    byte[] byteArray1 = new byte[length];
    byte[] byteArray2 = new byte[length];

    // Fill byteArray1 with 0 - 99.
    for (int i = 0; i < length; ++i)
    {
        byteArray1[i] = (byte)i;
    }

    // Display the first 10 elements in byteArray1.
    System.Console.WriteLine("The first 10 elements of the original are:");
    for (int i = 0; i < 10; ++i)
    {
        System.Console.Write(byteArray1[i] + " ");
    }
    System.Console.WriteLine("\n");

    // Copy the contents of byteArray1 to byteArray2.
    Copy(byteArray1, 0, byteArray2, 0, length);

    // Display the first 10 elements in the copy, byteArray2.
    System.Console.WriteLine("The first 10 elements of the copy are:");
    for (int i = 0; i < 10; ++i)
    {
        System.Console.Write(byteArray2[i] + " ");
    }
    System.Console.WriteLine("\n");

    // Copy the contents of the last 10 elements of byteArray1 to the
    // beginning of byteArray2.
    // The offset specifies where the copying begins in the source array.
    int offset = length - 10;
    Copy(byteArray1, offset, byteArray2, 0, length - offset);

    // Display the first 10 elements in the copy, byteArray2.
    System.Console.WriteLine("The first 10 elements of the copy are:");
    for (int i = 0; i < 10; ++i)
    {
        System.Console.Write(byteArray2[i] + " ");
    }
    System.Console.WriteLine("\n");
    /* Output:
        The first 10 elements of the original are:
        0 1 2 3 4 5 6 7 8 9

        The first 10 elements of the copy are:
        0 1 2 3 4 5 6 7 8 9

        The first 10 elements of the copy are:
        90 91 92 93 94 95 96 97 98 99
    */
}

曎新されたメモリ安党モデル (プレビュヌ)

Important

曎新されたメモリ安党モデルは、C# 15 および .NET 11 のプレビュヌ機胜です。 アクティブ化の手順に぀いおは、「 曎新されたメモリセヌフルヌルを有効にする」を参照しおください。 完党な蚭蚈に぀いおは、 メモリ安党機胜の仕様を参照しおください。

曎新されたモデルは、元のモデルが 1 ぀ずしお扱う 2 ぀の事柄 (ポむンタヌ コヌドの 存圚 ず呌び出し元ぞの安党矩務の 䌝達 ) を分離したす。 メンバヌ unsafe マヌクするず、そのメンバヌの本䜓内のポむンタヌが蚱可されるだけでなく、メンバヌ は安党ではなくなりたす。そのため、すべおの呌び出し元がその矩務を䌝達するか、怜蚌枈みの安党な呌び出し可胜な境界の背埌に排出する必芁がありたす。 この分離をサポヌトするために、モデルでは安党でないコンテキストも絞り蟌たれたす。ポむンタヌの存圚は安党ではありたせん。ランタむムが管理しおいないメモリにアクセスする操䜜だけです。 瞮小を䜿甚するず、安党なコヌドでポむンタヌを保持、枡し、返すこずができたすが、 unsafe は、メモリの安党性に実際に違反する可胜性がある操䜜ずメンバヌをマヌクしたす。

Requires-unsafe メンバヌ

元のモデルでは、メンバヌの unsafe 修食子は、メンバヌのシグネチャず本文内のポむンタヌのみを蚱可したす。 呌び出し元に安党性に぀いおは通知したせん。 曎新されたモデルは、呌び出し元に修食子の意味を䞎えたす。 メンバヌを unsafeマヌクするず、コンパむラはそれを 必須セヌフずしお扱いたす。すべおの呌び出し元は unsafe コンテキストから呌び出す必芁があり、その呌び出し元に安党を監査する矩務が移りたす。

メンバヌシグネチャの unsafe 修食子は、本文の安党でないコンテキストを確立しなくなりたした。 次の 2 ぀のロヌルが分割されたす。

  • 眲名の unsafe 修食子は、呌び出し元に矩務を䌝達したす。
  • 内郚 unsafe ブロックは、アンマネヌゞ メモリにアクセスする操䜜のスコヌプを蚭定したす。

次のプレビュヌの䟋では、 ReadInt32 は安党ではありたせん。 シグネチャは unsafe 修食子を持ち、内郚 unsafe ブロックは逆参照をラップしたす。

// Preview: requires the updated-memory-safety-rules feature.
public static unsafe int ReadInt32(byte* source)
{
    unsafe
    {
        return *(int*)source;
    }
}

呌び出し元は、独自の unsafe ブロックで呌び出しをラップしたす。

// Preview
unsafe
{
    int value = ReadInt32(buffer);
}

曎新されたモデルでは、いく぀かの関連ルヌルも匷化されたす。

  • unsafe修食子は、型宣蚀、静的コンストラクタヌ、およびファむナラむザヌで゚ラヌを生成したす。これは、修食子に通知する呌び出し元がないためです。
  • デリゲヌトは型型であるため、デリゲヌトを unsafeできたせん。
  • パラメヌタヌなしのコンストラクタヌが unsafe されおいる型は、基本型やフィヌルド型など、宣蚀内の new() たたは struct 制玄を満たすこずはできたせん。 匏では、安党でないコンテキスト内の制玄を満たすこずができたす。たずえば、Box<T>にnew()制玄があり、NativeResourceに必須の安党でないコンストラクタヌがある堎合にunsafe { var box = new Box<NativeResource>(); }したす。 このチェックは、拡匵ブロックず拡匵メ゜ッドの型匕数を含む、ゞェネリック型ずメ゜ッドのむンスタンス化に適甚されたす。 安党でないブロックは匏の䜿甚時に芁件を満たすこずができたすが、他の堎所では無効な基本型、フィヌルド型、たたぱむリアス宣蚀は修正されたせん。 宣蚀固有の修正に぀いおは、 CS9376 を参照しおください。

安党でないコンテキストを必芁ずする操䜜

ポむント先のメモリにアクセスする操䜜には、 unsafe コンテキストが必芁です。

  • ポむンタヌ間接参照 (*p)、ポむンタヌ メンバヌ アクセス (p->member)、ポむンタヌ芁玠アクセス (p[i])。
  • 関数ポむンタヌの呌び出し。
  • 固定サむズ バッファヌでの芁玠アクセス。

次の䟋では、 unsafe コンテキストを䜿甚せずに配列をピン留めしたすが、ポむンタヌを 1 ぀内で逆参照したす。

public static int ReadValue(int[] numbers)
{
    fixed (int* first = numbers)
    {
        // Dereferencing a pointer accesses unmanaged memory, so it still
        // requires an unsafe context.
        unsafe
        {
            return *first;
        }
    }
}

緩やかな操䜜

指し瀺されたメモリにアクセスしない操䜜では、 unsafe コンテキストが䞍芁になりたす。

  • ポむンタヌ型を宣蚀し、 & 挔算子を䜿甚しお倉数のアドレスを取埗したす。
  • 倉数をピン留めする fixed ステヌトメント。
  • stackalloc匏をポむンタヌに倉換する。
  • アンマネヌゞ型に適甚される sizeof 挔算子。

次の䟋では、 unsafe コンテキストなしでポむンタヌを䜜成しおピン留めしたす。

public static void CreatePointer()
{
    int value = 42;
    // Creating a pointer doesn't require an unsafe context.
    int* pointer = &value;
    int** pointerToPointer = &pointer;
}
public static void PinArray(int[] numbers)
{
    // The fixed statement no longer requires an unsafe context.
    fixed (int* first = numbers)
    {
        int* current = first;
    }
}

これらの緩和は、アセンブリが曎新されたメモリ安党芏則にオプトむンするかどうかに関係なく、 preview 蚀語バヌゞョンでコンパむルするたびに適甚されたす。

安党でない匏

unsafe匏unsafe(expression)、1 ぀の匏を評䟡するための安党でないコンテキストが䜜成されたす。 これは、フィヌルド初期化子、コンストラクタヌ初期化子、catch句のwhen句など、unsafe ブロックを構文的に衚瀺できない堎合に䟿利です。 安党でないコンテキストは閉じかっこで終わるので、含たれるステヌトメントの残りの郚分には拡匵されたせん。

次の䟋では、フィヌルド初期化子 ( unsafe ブロックを含めるこずができない䜍眮) 内のポむンタヌを逆参照したす。

// A field initializer can't contain an unsafe block, but it can contain an
// unsafe expression. The unsafe context ends at the closing parenthesis.
public static readonly int Signature = unsafe(ReadSignature());

private static unsafe int ReadSignature()
{
    int rawValue = 0x1234;
    int* pointer = &rawValue;
    return *pointer;
}

unsafe匏を䜿甚するず、await匏党䜓をunsafe ブロックにラップするのではなく、awaitの 1 ぀のオペランドに関する安党でないコンテキストのスコヌプを蚭定するこずもできたす。 await自䜓は安党でないコンテキストの倖郚にずどたりたす。埅機䞭のタスクを生成する呌び出しだけが、その䞭で実行されたす。

public static async Task<int> ReadSignatureAsync()
{
    // The unsafe expression scopes the unsafe context to the call that
    // produces the task. The 'await' itself stays outside that context.
    return await unsafe(ReadSignatureCoreAsync());
}

private static unsafe Task<int> ReadSignatureCoreAsync()
{
    int rawValue = 0x1234;
    int* pointer = &rawValue;
    return Task.FromResult(*pointer);
}

他の安党でないコヌドず同様に、 unsafe 匏には AllowUnsafeBlocks コンパむラ オプションが必芁であり、 preview 蚀語バヌゞョンが必芁です。

排出には安党でない矩務が必芁

必須の安党でない操䜜を呌び出すメンバヌには、矩務を䌝達するか、それを排出するかの 2 ぀の遞択肢がありたす。

  • 䌝達: 独自のメンバヌ unsafeをマヌクしたす。 矩務は呌び出し元に枡されたす。 自分で矩務を完党に怜蚌できない堎合は、䌝達を䜿甚したす。
  • 退院: メンバヌの眲名は安党なたたにしおおきたす。 メンバヌ内の矩務 (通垞はランタむム ガヌドを䜿甚) を怜蚌し、内郚 unsafe ブロックで安党でない操䜜を実行したす。 内郚 unsafe ブロックを含むが、独自の眲名をマヌクしないメンバヌ unsafe は 安党でない境界です。安党でないコヌドを安党に呌び出し可胜なサヌフェスに倉換したす。

次のプレビュヌ モックアップでは、ガヌドを䜿甚しおその入力を怜蚌し、マネヌゞド配列をピン留めしお、ポむンタヌを読み取りたす。 呌び出し元は unsafe コンテキストを必芁ずしたせん。これは、メ゜ッドが矩務を負うためです。

// Preview
public static int SumBytes(byte[] source)
{
    ArgumentNullException.ThrowIfNull(source);

    fixed (byte* first = source)
    {
        unsafe
        {
            // SAFETY: the null check and source.Length bound every read to the pinned array.
            int total = 0;
            for (int i = 0; i < source.Length; i++)
            {
                total += first[i];
            }

            return total;
        }
    }
}

null チェックず配列の長さは、読み取りがバッファヌを越えお実行される入力を陀倖するため、 unsafe ブロック内の逆参照はサりンドになりたす。 このメ゜ッドは残䜙矩務を負いたせん。そのため、安党な呌び出し可胜な眲名が公開されたす。

安党性に関するドキュメント

必須の安党でないメンバヌは、呌び出し元が保蚌する必芁がある内容を文曞化する必芁がありたす。 曎新されたモデルでは、次の 2 ぀の補完的なコメント スタむルが掚奚されたす。

  • 眲名の䞊にある /// <safety> ドキュメント ブロックには、正匏なコントラクト (呌び出し元が満たす必芁がある条件) が蚘茉されおいたす。 アナラむザヌは、䞍足しおいる 必須の安党でない メンバヌにフラグを蚭定できたす。
  • // SAFETY: ブロック内のunsafeコメントは、本文を読む開発者ず監査者のために、その堎所で操䜜が鳎る理由を蚘録したす。

次のプレビュヌの䟋は、 requires-unsafeReadByte メ゜ッドの䞡方のスタむルを瀺しおいたす。

// Preview
/// <summary>Reads a single byte from unmanaged memory.</summary>
/// <safety>
/// The sum of <paramref name="ptr"/> and <paramref name="offset"/> must address a byte
/// the caller is permitted to read.
/// </safety>
public static unsafe byte ReadByte(IntPtr ptr, int offset)
{
    byte* address = (byte*)ptr;
    unsafe
    {
        // SAFETY: relies on the caller obligation stated in the <safety> block.
        return address[offset];
    }
}

/// <safety> ブロックによっおコントラクトが通知されたす。 コントラクトは、すべおの呌び出し元ずレビュヌ担圓者が参照するドキュメントに属したす。

安党でないフィヌルド

フィヌルドの unsafe 修食子は、宣蚀された型が、倖偎の型が保持し、他のコヌドが䟝存するコントラクトを衚しおいない堎合に䜿甚したす。 安党でないのは、型システムが芋るものず型が玄束するものの間のギャップに存圚したす。 この修食子は、フィヌルドぞのすべおの曞き蟌みを unsafe ブロックに匷制し、曞き蟌みを 1 か所で確認できるようにしたす。

最も明確なケヌスは、ネむティブ ポむンタヌを保持するフィヌルドです。 ポむンタヌは、 System.Span<T> ず同じようにアドレス指定するバむト数を宣蚀しないため、栌玍型はその情報自䜓を保持したす。

// Preview
public class NativeBuffer
{
    /// <safety>
    /// Null, or points to a buffer of Length bytes.
    /// </safety>
    private unsafe byte* _pointer;

    public int Length { get; }

    public byte ReadAt(int index)
    {
        ArgumentOutOfRangeException.ThrowIfNegative(index);
        ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(index, Length);
        unsafe
        {
            // SAFETY: the bounds checks confine the read to the buffer that _pointer addresses.
            return _pointer[index];
        }
    }
}

readonly unsafe フィヌルドは、コントラクトず組み蟌みのガヌドを組み合わせおいたす。unsafeむンバリアントに名前を付けたす。readonly、構築埌に曞き蟌みが䞭断されるのを防ぎたす。 プロパティたたはむベント unsafe をマヌクしおも、バッキング フィヌルドに 安党ではない必芁はありたせん。

曎新されたルヌルの䞋で、型内のすべおのむンスタンス フィヌルドを、 safe たたは unsafeの明瀺的たたは拡匵レむアりトでマヌクしたす。 これらの型は、LayoutKind.Explicitに蚭定StructLayoutAttribute、たたはExtendedLayoutAttributeでマヌクされたす。 0 以倖の StructLayoutAttribute.Pack たたは StructLayoutAttribute.Sizeを持぀型のむンスタンス フィヌルドに同じ修食子を適甚したす (シヌケンシャルたたは自動レむアりトの堎合でも)。 指定されおいない倀たたはれロの倀は、それ自䜓で远加の芁件をトリガヌしたせん。 静的フィヌルドず定数フィヌルドは陀倖されたす。 合成されたバッキング フィヌルドの所有プロパティたたはむベントに泚釈を付けたす。

暗黙的に生成されるストレヌゞの蚺断ず修正に぀いおは、「 CS9392: フィヌルド レむアりトの安党性」を参照しおください。

safe キヌワヌド

曎新されたモデルでは、宣蚀を蚌明する safe コンテキスト キヌワヌドが远加されたす。これは、コンパむラが遞択を明瀺的に行う必芁がある堎合に正しいかどうかを蚌明したす。

通垞の宣蚀は既定で安党であるため、 safe 修食子は必芁ありたせん。 unsafeコヌドはたれであるため、ほずんどのコヌドベヌスでは、既定倀が安党でない堎合やコンパむラが明瀺的な遞択を必芁ずする堎合にのみ、safeを䜿甚したす。 この修食子は、宣蚀を必須でない安党でないものずしおマヌクしたす。安党な字句コンテキストを確立したり、囲たれおいる安党でないコンテキストを取り消したりするこずはありたせん。 同じ宣蚀で safe ず unsafe を組み合わせるこずはできたせん。 新しいコンパむラでは、 unsafe 宣蚀修食子ず safe 宣蚀修食子の䞡方に、曎新された芏則の適甚オプトむンずは別に AllowUnsafeBlocks オプションが必芁です。 これを指定しない堎合、宣蚀修食子は CS9401 を生成し、安党でないブロックでは CS0227 が生成され続けたす。 Roslyn は、.NET 11.0.100 以降、Visual Studio 2026 バヌゞョン 18.13 で導入された修食子チェックを文曞化したす。 .NET 11 コンパむラは、safeにこのオプションを必芁ずせず、unsafe宣蚀修食子に CS0227 を䜿甚したす。 詳现に぀いおは、 安党でないコンテキストの芁件 に関するペヌゞを参照しおください。 メモリ セヌフ モデル自䜓はプレビュヌ機胜のたたです。

extern メンバヌはネむティブ コヌドを呌び出すので、コンパむラはその安党性を分類できたせん。 曎新されたモデルでは、externたたはLibraryImportのいずれかの郚分メ゜ッドsafe含め、すべおのunsafe宣蚀をマヌクしたす。

// Preview
[LibraryImport("libc")]
internal static safe partial int getpid();

[LibraryImport("libc", StringMarshalling = StringMarshalling.Utf8)]
internal static unsafe partial nint strlen(byte* str);

getpid はパラメヌタヌを受け取らず、プリミティブを返すので、䜜成者は呌び出しが安党であるこずを蚌明し、呌び出し元は匏なしでそれを䜿甚したす。 strlen はネむティブ コヌドが逆参照する生ポむンタヌを受け取るので、宣蚀は unsafe され、呌び出し元に矩務が䌝達されたす。 䞡方の修食子を省略するず゚ラヌが発生するため、安党䞊の刀断が匷制されたす。 明瀺的たたは拡匵レむアりトの型のフィヌルドでは、同じ芏則が䜿甚されたす。

C# 蚀語仕様

詳现に぀いおは、C# 蚀語仕様の安党でないコヌドの章を参照しおください。

曎新されたメモリ安党モデルの蚭蚈に぀いおは、 メモリ安党機胜の仕様を参照しおください。

こちらも参照ください