安全で効率的な C# コードを記述するWrite safe and efficient C# code

C# の新しい機能により、よりよいパフォーマンスの検証可能なセーフ コードを記述できます。New features in C# enable you to write verifiable safe code with better performance. これらの手法を慎重に適用した場合、アンセーフ コードが必要なシナリオが少なくなります。If you carefully apply these techniques, fewer scenarios require unsafe code. これらの機能により、メソッドの引数およびメソッドの戻り値として、これまでより簡単に値の型への参照を使用できるようになります。These features make it easier to use references to value types as method arguments and method returns. これらの手法を安全に使用すると、値の型のコピーが最小限に抑えられます。When done safely, these techniques minimize copying value types. 値の型を使用することで、割り当てとガベージ コレクション パスの数が最小限になります。By using value types, you can minimize the number of allocations and garbage collection passes.

この記事にあるサンプル コードの多くでは、C# 7.2 で追加された機能が使用されています。Much of the sample code in this article uses features added in C# 7.2. そのような機能を使用するには、C# 7.2 以降を使用するようにプロジェクトを構成する必要があります。To use those features, you must configure your project to use C# 7.2 or later. 言語バージョンを設定する方法の詳細については、言語バージョンの構成に関する記事を参照してください。For more information on setting the language version, see configure the language version.

この記事では、効率的なリソース管理の手法に焦点を当てます。This article focuses on techniques for efficient resource management. 値の型を利用する利点の 1 つは、多くの場合にヒープ割り当てが回避されることです。One advantage to using value types is that they often avoid heap allocations. 欠点は、値でコピーされるということです。The disadvantage is that they're copied by value. このトレードオフは、大量のデータを操作するアルゴリズムの最適化を難しくします。This tradeoff makes it harder to optimize algorithms that operate on large amounts of data. C# 7.2 の新しい言語機能では、値の型への参照を使用して安全で効率的なコードを作成できるメカニズムが提供されます。New language features in C# 7.2 provide mechanisms that enable safe efficient code using references to value types. これらの機能を賢く使って、割り当てとコピー操作の両方を最小限に抑えます。Use these features wisely to minimize both allocations and copy operations. この記事では、これらの新しい機能について説明します。This article explores those new features.

この記事では、以下のリソース管理手法に焦点を当てます。This article focuses on the following resource management techniques:

  • readonly struct を宣言して型が変更不可であること表し、コンパイラが in パラメーターを使用するときにコピーを保存できるようにします。Declare a readonly struct to express that a type is immutable and enables the compiler to save copies when using in parameters.
  • 型を変更できない場合は、メンバーが状態を変更しないことを示すために、struct メンバーに readonly を宣言します。If a type can't be immutable, declare struct members readonly to indicate that the member doesn't modify state.
  • 戻り値が IntPtr.Size より大きい struct であり、ストレージの有効期間が値を返すメソッドより長い場合に、ref readonly を使用して戻します。Use a ref readonly return when the return value is a struct larger than IntPtr.Size and the storage lifetime is greater than the method returning the value.
  • readonly struct のサイズが IntPtr.Size より大きいときは、パフォーマンスのため、in として渡す必要があります。When the size of a readonly struct is bigger than IntPtr.Size, you should pass it as an in parameter for performance reasons.
  • readonly 修飾子で宣言されている場合、またはメソッドが構造体の readonly メンバーのみを呼び出す場合を除き、in パラメーターとして struct は渡さないでください。Never pass a struct as an in parameter unless it's declared with the readonly modifier or the method calls only readonly members of the struct. このガイダンスに違反すると、パフォーマンスが低下し、動作が不明瞭になる場合があります。Violating this guidance may negatively affect performance and could lead to an obscure behavior.
  • バイトのシーケンスとしてメモリを操作するには、ref struct または Span<T>ReadOnlySpan<T> などの readonly ref struct を使用します。Use a ref struct, or a readonly ref struct such as Span<T> or ReadOnlySpan<T> to work with memory as a sequence of bytes.

これらの手法では、参照に関する 2 つの相反する目標のバランスを取ることが強要されます。These techniques force you to balance two competing goals with regard to references and values. 参照型の変数では、メモリ内の場所への参照が保持されます。Variables that are reference types hold a reference to the location in memory. 値の型の変数には、値が直接格納されます。Variables that are value types directly contain their value. これらの違いにより、メモリ リソースを管理するために重要となる主な違いが強調されます。These differences highlight the key differences that are important for managing memory resources. 値の型は、通常、メソッドに渡されるとき、またはメソッドから戻されるときに、コピーされます。Value types are typically copied when passed to a method or returned from a method. この動作には、値の型のメンバーを呼び出すときの、this の値のコピーが含まれます。This behavior includes copying the value of this when calling members of a value type. コピーのコストは、型のサイズに関係します。The cost of the copy is related to the size of the type. 参照型は、マネージド ヒープ上に割り当てられます。Reference types are allocated on the managed heap. 新しいオブジェクトごとに新しく割り当てる必要があり、後でそれを回収する必要があります。Each new object requires a new allocation, and subsequently must be reclaimed. どちらの操作にも時間がかかります。Both these operations take time. 参照型が引数としてメソッドに渡されるとき、またはメソッドから戻されるときは、参照がコピーされます。The reference is copied when a reference type is passed as an argument to a method or returned from a method.

この記事では、次に示す 3 次元の点の構造体を概念の例として使用し、これらの推奨事項を説明します。This article uses the following example concept of the 3D-point structure to explain these recommendations:

public struct Point3D
{
    public double X;
    public double Y;
    public double Z;
}

別の例では、この概念の異なる実装が使用されます。Different examples use different implementations of this concept.

変更不可の値の型用に読み取り専用の構造体を宣言するDeclare readonly structs for immutable value types

readonly 修飾子を使用して struct を宣言すると、変更不可の型を作成することが意図であることがコンパイラに伝わります。Declaring a struct using the readonly modifier informs the compiler that your intent is to create an immutable type. コンパイラでは、以下の規則に従ってその設計の決定が適用されます。The compiler enforces that design decision with the following rules:

  • すべてのフィールドのメンバーは readonly でなければならないAll field members must be readonly
  • 自動的に実装されるプロパティも含めて、すべてのプロパティは読み取り専用でなければならない。All properties must be read-only, including auto-implemented properties.

これら 2 つの規則は、readonly struct のメンバーによってその構造体の状態が変更されないことを保証するのに十分です。These two rules are sufficient to ensure that no member of a readonly struct modifies the state of that struct. struct は変更不可です。The struct is immutable. 次の例で示すように、Point3D 構造体を変更不可の構造体として定義できます。The Point3D structure could be defined as an immutable struct as shown in the following example:

readonly public struct ReadonlyPoint3D
{
    public ReadonlyPoint3D(double x, double y, double z)
    {
        this.X = x;
        this.Y = y;
        this.Z = z;
    }

    public double X { get; }
    public double Y { get; }
    public double Z { get; }
}

変更不可の値の型を作成することが設計の意図である場合は常に、この推奨事項に従ってください。Follow this recommendation whenever your design intent is to create an immutable value type. パフォーマンスの向上はすべて付加的なメリットです。Any performance improvements are an added benefit. readonly struct の機能は、設計の意図を明確に表現することです。The readonly struct clearly expresses your design intent.

構造体を変更不可にできない場合、読み取り専用メンバーを宣言するDeclare readonly members when a struct can't be immutable

C# 8.0 以降で構造体の型が変更可能な場合、変更を起こさないメンバーを readonly に宣言する必要があります。In C# 8.0 and later, when a struct type is mutable, you should declare members that don't cause mutation to be readonly. たとえば、3D ポイントの構造体の変更可能なバリエーションは次のとおりです。For example, the following is a mutable variation of the 3D point structure:

public struct Point3D
{
    public Point3D(double x, double y, double z)
    {
        this.X = x;
        this.Y = y;
        this.Z = z;
    }

    private double _x;
    public double X 
    { 
        readonly get { return _x;}; 
        set { _x = value; }
    }
    
    private double _y;
    public double Y 
    { 
        readonly get { return _y;}; 
        set { _y = value; }
    }

    private double _z;
    public double Z 
    { 
        readonly get { return _z;}; 
        set { _z = value; }
    }

    public readonly double Distance => Math.Sqrt(X * X + Y * Y + Z * Z);

    public readonly override string ToString() => $"{X, Y, Z }";
}

上の例では、readonly 修飾子を適用できる多数の場所を多く示しています (メソッド、プロパティ、およびプロパティ アクセサー)。The preceding sample shows many of the locations where you can apply the readonly modifier: methods, properties, and property accessors. 自動実装プロパティを使用する場合、コンパイラは読み取り/書き込みプロパティに対し、get アクセサーに readonly 修飾子を追加します。If you use auto-implemented properties, the compiler adds the readonly modifier to the get accessor for read-write properties. コンパイラは、get アクセサーのみを持つプロパティに対し、readonly 修飾子を、自動実装プロパティの宣言に追加します。The compiler adds the readonly modifier to the auto-implemented property declarations for properties with only a get accessor.

状態が変更不可なメンバーに readonly 修飾子を追加すると、2 つの関連する利点があります。Adding the readonly modifier to members that don't mutate state provides two related benefits. まず、コンパイラによって意図が適用されます。First, the compiler enforces your intent. そのメンバーは構造体の状態を変更することも、readonly とマークされていないメンバーにもアクセスできません。That member can't mutate the struct's state, nor can it access a member that isn't also marked readonly. 2 つ目には、readonly メンバーにアクセスするときに、コンパイラは in パラメーターの防御的なコピーを作成しません。Second, the compiler won't create defensive copies of in parameters when accessing a readonly member. コンパイラは、readonly メンバーによって struct が変更されないことを保証するため、この最適化を安全に行うことができます。The compiler can make this optimization safely because it guarantees that the struct is not modified by a readonly member.

可能であれば大きい構造体には ref readonly return ステートメントを使用するUse ref readonly return statements for large structures when possible

返される値が返すメソッドに対してローカルでない場合は、参照渡しで値を返すことができます。You can return values by reference when the value being returned isn't local to the returning method. 参照渡しで返すということは、構造体ではなく参照のみがコピーされることを意味します。Returning by reference means that only the reference is copied, not the structure. 次の例の Origin プロパティでは、返される値がローカル変数であるため、ref 返しを使用することはできません。In the following example, the Origin property can't use a ref return because the value being returned is a local variable:

public Point3D Origin => new Point3D(0,0,0);

一方、次のプロパティ定義では、返される値が静的メンバーであるため、参照渡しで返すことができます。However, the following property definition can be returned by reference because the returned value is a static member:

public struct Point3D
{
    private static Point3D origin = new Point3D(0,0,0);

    // Dangerous! returning a mutable reference to internal storage
    public ref Point3D Origin => ref origin;

    // other members removed for space
}

呼び出し元によって元の値が変更されては困るので、readonly ref で値を返す必要があります。You don't want callers modifying the origin, so you should return the value by readonly ref:

public struct Point3D
{
    private static Point3D origin = new Point3D(0,0,0);

    public static ref readonly Point3D Origin => ref origin;

    // other members removed for space
}

ref readonly を返すと、もっと大きい構造体をコピーしなくて済み、内部データ メンバーの変更不可性を維持することができます。Returning ref readonly enables you to save copying larger structures and preserve the immutability of your internal data members.

呼び出しサイトでは、Origin プロパティを readonly ref または値のどちらとして使用するかを、呼び出し元が選択します。At the call site, callers make the choice to use the Origin property as a readonly ref or as a value:

var originValue = Point3D.Origin;
ref readonly var originReference = ref Point3D.Origin;

先のコードの最初の割り当てでは、Origin 定数のコピーが作成され、そのコピーが割り当てられます。The first assignment in the preceding code makes a copy of the Origin constant and assigns that copy. 2 つ目は参照を割り当てます。The second assigns a reference. readonly 修飾子は変数の宣言の一部にする必要があります。Notice that the readonly modifier must be part of the declaration of the variable. それが参照するものは変更できません。The reference to which it refers can't be modified. 変更を試みると、コンパイル時エラーが発生します。Attempts to do so result in a compile-time error.

originReference の宣言では、readonly 修飾子が必要です。The readonly modifier is required on the declaration of originReference.

コンパイラでは、呼び出し元で参照を変更できないように強制されます。The compiler enforces that the caller can't modify the reference. 値を直接割り当てようとすると、コンパイル時エラーが生成されます。Attempts to assign the value directly generate a compile-time error. ただし、メンバー メソッドによって構造体の状態が変更されるかどうかは、コンパイラでは認識できません。However, the compiler can't know if any member method modifies the state of the struct. オブジェクトが変更されないように、コンパイラではコピーが作成され、そのコピーを使用してメンバー参照が呼び出されます。To ensure that the object isn't modified, the compiler creates a copy and calls member references using that copy. 変更されるとすれば、その防御用のコピーに行われます。Any modifications are to that defensive copy.

System.IntPtr.Size より大きい readonly struct パラメーターに in 修飾子を適用するApply the in modifier to readonly struct parameters larger than System.IntPtr.Size

in キーワードは、既存の ref キーワードと out キーワードを補完し、引数を参照で渡します。The in keyword complements the existing ref and out keywords to pass arguments by reference. in キーワードでは、引数を参照で渡すことが指定されますが、呼び出されたメソッドでは値は変更されません。The in keyword specifies passing the argument by reference, but the called method doesn't modify the value.

この追加によって、設計の意図を表すためのボキャブラリが完全に与えられます。This addition provides a full vocabulary to express your design intent. メソッド シグネチャで次の修飾子のいずれも指定しないのであれば、呼び出されたメソッドに渡されるとき、値の型がコピーされます。Value types are copied when passed to a called method when you don't specify any of the following modifiers in the method signature. これらのどの修飾子を使用しても、変数を参照で渡すことが指定され、コピーが回避されます。Each of these modifiers specifies that a variable is passed by reference, avoiding the copy. 修飾子はそれぞれ、異なる意図を表します。Each modifier expresses a different intent:

  • out:このメソッドでは、このパラメーターとして使用される引数の値が設定されます。out: This method sets the value of the argument used as this parameter.
  • ref:このメソッドでは、このパラメーターとして使用される引数の値が設定されることがあります。ref: This method may set the value of the argument used as this parameter.
  • in:このメソッドでは、このパラメーターとして使用される引数の値は変更されません。in: This method doesn't modify the value of the argument used as this parameter.

in 修飾子を追加し、参照で引数を渡し、参照で引数を渡して不必要なコピーを回避する設計の意図を宣言します。Add the in modifier to pass an argument by reference and declare your design intent to pass arguments by reference to avoid unnecessary copying. その引数として使用されるオブジェクトを変更することは、意図されていません。You don't intend to modify the object used as that argument.

この方法では、多くの場合、IntPtr.Size より大きい読み取り専用の値の型でのパフォーマンスが向上します。This practice often improves performance for readonly value types that are larger than IntPtr.Size. 単純型 (sbytebyteshortushortintuintlongulongcharfloatdoubledecimalboolenum 型) の場合、可能性のあるパフォーマンスの向上は最小限です。For simple types (sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal and bool, and enum types), any potential performance gains are minimal. 実際には、IntPtr.Size より小さい型に対して参照渡しを使用すると、パフォーマンスが低下する可能性があります。In fact, performance may degrade by using pass-by-reference for types smaller than IntPtr.Size.

次のコードは、3D 空間の 2 点間の距離を計算するメソッドの例です。The following code shows an example of a method that calculates the distance between two points in 3D space.

private static double CalculateDistance(in Point3D point1, in Point3D point2)
{
    double xDifference = point1.X - point2.X;
    double yDifference = point1.Y - point2.Y;
    double zDifference = point1.Z - point2.Z;

    return Math.Sqrt(xDifference * xDifference + yDifference * yDifference + zDifference * zDifference);
}

引数は 2 つの構造で、それぞれに 3 つの倍精度浮動小数点型が含まれます。The arguments are two structures that each contain three doubles. 倍精度浮動小数点型は 8 バイトです。そのため、各引数は 24 バイトになります。A double is 8 bytes, so each argument is 24 bytes. in 修飾子を指定することで、コンピューターのアーキテクチャに基づき、4 バイトまたは 8 バイトの参照をそれらの引数に渡します。By specifying the in modifier, you pass a 4 byte or 8-byte reference to those arguments, depending on the architecture of the machine. サイズの差はわずかですが、アプリケーションにおいて、多くの異なる値を使用する短いループでこのメソッドを呼び出すと膨れあがります。The difference in size is small, but it adds up when your application calls this method in a tight loop using many different values.

in 修飾子は、その他の面でも outref を補完します。The in modifier complements out and ref in other ways as well. inout、または ref の存在のみが異なるメソッドのオーバーロードは作成できません。You can't create overloads of a method that differ only in the presence of in, out, or ref. これらの新しいルールは、out パラメーターと ref パラメーターに常に定義されていた同じ動作を拡張します。These new rules extend the same behavior that had always been defined for out and ref parameters. outref 修飾子のように、in 修飾子が適用されるため、値の型はボックス化されません。Like the out and ref modifiers, value types aren't boxed because the in modifier is applied.

in 修飾子は、メソッド、デリケート、ラムダ、ローカル関数、インデクサー、演算子など、パラメーターを受け取るあらゆるメンバーに適用されることがあります。The in modifier may be applied to any member that takes parameters: methods, delegates, lambdas, local functions, indexers, operators.

in パラメーターのもう 1 つの機能として、in パラメーターへの引数にリテラル値または定数を使用できます。Another feature of in parameters is that you may use literal values or constants for the argument to an in parameter. また、ref パラメーターや out パラメーターとは異なり、呼び出しサイトで in 修飾子を適用する必要はありません。Also, unlike a ref or out parameter, you don't need to apply the in modifier at the call site. 次のコードは、CalculateDistance メソッドを呼び出す 2 つの例です。The following code shows you two examples of calling the CalculateDistance method. 最初のメソッドでは、参照で渡される 2 つのローカル変数が使用されます。The first uses two local variables passed by reference. 2 つ目のメソッドには、メソッド呼び出しの一部として作成される一時的な変数が含まれます。The second includes a temporary variable created as part of the method call.

var distance = CalculateDistance(pt1, pt2);
var fromOrigin = CalculateDistance(pt1, new Point3D());

コンパイラでは、in 引数の読み取り専用の性質を強制する方法がいくつかあります。There are several ways in which the compiler enforces the read-only nature of an in argument. まず、呼び出されたメソッドは in パラメーターに直接割り当てできません。First of all, the called method can't directly assign to an in parameter. 値が struct 型の場合、in パラメーターのどのフィールドにも直接割り当てできません。It can't directly assign to any field of an in parameter when that value is a struct type. また、ref または out 修飾子を使用するメソッドに、in パラメーターを渡すことはできません。In addition, you can't pass an in parameter to any method using the ref or out modifier. これらの規則は、in パラメーターのすべてのフィールドに適用されます (ただし、フィールドが struct 型でパラメーターも struct 型の場合)。These rules apply to any field of an in parameter, provided the field is a struct type and the parameter is also a struct type. 実際、これらの規則は、メンバー アクセスのすべてのレベルで型が structs であれば、複数層のメンバー アクセスに適用されます。In fact, these rules apply for multiple layers of member access provided the types at all levels of member access are structs. コンパイラは struct 型を in 引数として渡し、その struct メンバーが他のメソッドへの引数として使用される場合は読み取り専用変数になるよう強制します。The compiler enforces that struct types passed as in arguments and their struct members are read-only variables when used as arguments to other methods.

in パラメーターを使用することで、コピーを作成することの潜在的なパフォーマンス コストを回避できます。The use of in parameters can avoid the potential performance costs of making copies. メソッド呼び出しのセマンティクスは変更されません。It doesn't change the semantics of any method call. そのため、呼び出しサイトで in 修飾子を指定する必要はありません。Therefore, you don't need to specify the in modifier at the call site. 呼び出しサイトで in 修飾子を省略すると、次の理由で、引数のコピーを作成することが許可されていることがコンパイラに通知されます。Omitting the in modifier at the call site informs the compiler that it's allowed to make a copy of the argument for the following reasons:

  • 暗黙的な変換は存在するが、引数の型からパラメーターの型への ID 変換が存在しない。There exists an implicit conversion but not an identity conversion from the argument type to the parameter type.
  • 引数は式だが、既知のストレージ変数がない。The argument is an expression but doesn't have a known storage variable.
  • in の有無によって異なるオーバーロードが存在する。An overload exists that differs by the presence or absence of in. この場合は、値渡しオーバーロードの方がより適しています。In that case, the by value overload is a better match.

これらの規則は、既存のコードを読み取り専用の参照引数を使用するように更新するときに役立ちます。These rules are useful as you update existing code to use read-only reference arguments. 呼び出されるメソッド内で、値渡しパラメーターを使用する任意のインスタンス メソッドを呼び出すことができます。Inside the called method, you can call any instance method that uses by value parameters. それらのインスタンスで、in パラメーターのコピーが作成されます。In those instances, a copy of the in parameter is created. コンパイラは in パラメーターに一時的な変数を作成できるため、in パラメーターに既定値を指定することもできます。Because the compiler may create a temporary variable for any in parameter, you can also specify default values for any in parameter. 次のコードでは、2 つ目の点の既定値として原点 (点 0,0) を指定します。The following code specifies the origin (point 0,0) as the default value for the second point:

private static double CalculateDistance2(in Point3D point1, in Point3D point2 = default)
{
    double xDifference = point1.X - point2.X;
    double yDifference = point1.Y - point2.Y;
    double zDifference = point1.Z - point2.Z;

    return Math.Sqrt(xDifference * xDifference + yDifference * yDifference + zDifference * zDifference);
}

コンパイラに読み取り専用引数の参照渡しを強制するには、次のコードに示すように、呼び出しサイトで引数に in 修飾子を指定します。To force the compiler to pass read-only arguments by reference, specify the in modifier on the arguments at the call site, as shown in the following code:

distance = CalculateDistance(in pt1, in pt2);
distance = CalculateDistance(in pt1, new Point3D());
distance = CalculateDistance(pt1, in Point3D.Origin);

この動作により、パフォーマンスの向上が可能な大規模なコードベースで、徐々に in パラメーターを採用しやすくなります。This behavior makes it easier to adopt in parameters over time in large codebases where performance gains are possible. 最初に、メソッド シグネチャに in 修飾子を追加します。You add the in modifier to method signatures first. その後、呼び出しサイトで in 修飾子を追加し、readonly struct 型を作成して、コンパイラに他の場所で in パラメーターの防御用コピーを作成しないようにすることができます。Then, you can add the in modifier at call sites and create readonly struct types to enable the compiler to avoid creating defensive copies of in parameters in more locations.

in パラメーターの指定は、参照型または数値と併用することもできます。The in parameter designation can also be used with reference types or numeric values. ただし、いずれの場合も、利点があるとしてもわずかです。However, the benefits in both cases are minimal, if any.

in 引数として変更可能な構造体を使用しないNever use mutable structs as in in argument

上で説明した手法では、参照を返し、参照で値を渡すことにより、コピーを避ける方法が説明されています。The techniques described above explain how to avoid copies by returning references and passing values by reference. これらの手法は、引数の型が readonly struct 型として宣言されているときに最善の結果が得られます。These techniques work best when the argument types are declared as readonly struct types. そうでない場合は、多くの状況において、引数の読み取り専用性を強制するために、コンパイラで防御用コピーを作成する必要があります。Otherwise, the compiler must create defensive copies in many situations to enforce the readonly-ness of any arguments. 原点からの 3D の点の距離を計算する以下の例について考えます。Consider the following example that calculates the distance of a 3D point from the origin:

private static double CalculateDistance(in Point3D point1, in Point3D point2)
{
    double xDifference = point1.X - point2.X;
    double yDifference = point1.Y - point2.Y;
    double zDifference = point1.Z - point2.Z;

    return Math.Sqrt(xDifference * xDifference + yDifference * yDifference + zDifference * zDifference);
}

Point3D 構造体は、読み取り専用の構造体では "ありません"。The Point3D structure is not a readonly struct. このメソッドの本体には、6 つの異なるプロパティ アクセス呼び出しがあります。There are six different property access calls in the body of this method. 最初の調査では、これらのアクセスは安全であると思ったかもしれません。On first examination, you may have thought these accesses were safe. いずれにしても、get アクセサーではオブジェクトの状態を変更すべきではありません。After all, a get accessor shouldn't modify the state of the object. しかし、それを強制する言語規則はありません。But there's no language rule that enforces that. それは、一般的な規則のみです。It's only a common convention. すべての型で、内部状態を変更する get アクセサーを実装できます。Any type could implement a get accessor that modified the internal state. 何らかの言語的保証がないと、メンバーを呼び出す前に、コンパイラで引数の一時コピーを作成する必要があります。Without some language guarantee, the compiler must create a temporary copy of the argument before calling any member. スタック上に一時的なストレージが作成され、引数の値が一時的なストレージにコピーされて、this 引数としての各メンバー アクセスに対して値がスタックにコピーされます。The temporary storage is created on the stack, the values of the argument are copied to the temporary storage, and the value is copied to the stack for each member access as the this argument. 多くの場合、これらのコピーによってパフォーマンスが悪影響を受けるので、引数の型が readonly struct ではない場合は、値渡しの方が、読み取り専用参照渡しより速くなります。In many situations, these copies harm performance enough that pass-by-value is faster than pass-by-readonly-reference when the argument type isn't a readonly struct.

代わりに、距離の計算で変更不可の構造体 ReadonlyPoint3D が使用されている場合は、一時オブジェクトは必要ありません。Instead, if the distance calculation uses the immutable struct, ReadonlyPoint3D, temporary objects aren't needed:

private static double CalculateDistance3(in ReadonlyPoint3D point1, in ReadonlyPoint3D point2 = default)
{
    double xDifference = point1.X - point2.X;
    double yDifference = point1.Y - point2.Y;
    double zDifference = point1.Z - point2.Z;

    return Math.Sqrt(xDifference * xDifference + yDifference * yDifference + zDifference * zDifference);
}

コンパイラでは、readonly struct のメンバーの呼び出しに対してもっと効率的なコードが生成されます。this 参照は、レシーバーのコピーではなく、常に、メンバー メソッドに参照で渡される in パラメーターです。The compiler generates more efficient code when you call members of a readonly struct: The this reference, instead of a copy of the receiver, is always an in parameter passed by reference to the member method. この最適化によって、in 引数として readonly struct を使用するときのコピーが減ります。This optimization saves copying when you use a readonly struct as an in argument.

null 許容値の型を in 引数として渡すことはできません。You shouldn't pass a nullable value type as an in argument. Nullable<T> 型は、読み取り専用の構造体として宣言されていません。The Nullable<T> type isn't declared as a read-only struct. つまり、コンパイラは、パラメータ―宣言上で in 修飾子を使用してメソッドに渡される null 許容値型の任意の引数に対して、防御用のコピーを生成する必要があります。That means the compiler must generate defensive copies for any nullable value type argument passed to a method using the in modifier on the parameter declaration.

GitHub のサンプル リポジトリBenchmark.net を使用して、パフォーマンスの違いを示すサンプル プログラムを確認できます。You can see an example program that demonstrates the performance differences using Benchmark.net in our samples repository on GitHub. 変更可能な構造体の値渡しと参照渡し、および変更不可の構造体の値渡しと参照渡しが比較されています。It compares passing a mutable struct by value and by reference with passing an immutable struct by value and by reference. 変更不可の構造体で参照渡しを使用したときが、最も高速です。The use of the immutable struct and pass by reference is fastest.

単一のスタック フレームでブロックまたはメモリを操作するために ref struct 型を使用するUse ref struct types to work with blocks or memory on a single stack frame

関連するもう 1 つの言語機能は、単一のスタック フレームに拘束される必要のある値型を宣言する機能です。A related language feature is the ability to declare a value type that must be constrained to a single stack frame. この制限により、コンパイラでいくつかの最適化を行うことができます。This restriction enables the compiler to make several optimizations. この機能の第一の動機は Span<T> と関連構造でした。The primary motivation for this feature was Span<T> and related structures. Span<T> 型を利用する新規および更新された .NET API を使用することにより、これらの機能強化によるパフォーマンスの向上を実現できます。You'll achieve performance improvements from these enhancements by using new and updated .NET APIs that make use of the Span<T> type.

stackalloc で作成したメモリを使用するとき、あるいは相互運用 API からメモリを使用するとき、同様の要件が求められる場合があります。You may have similar requirements working with memory created using stackalloc or when using memory from interop APIs. そのようなニーズには独自の ref struct 型を定義できます。You can define your own ref struct types for those needs.

readonly ref structreadonly ref struct type

構造体を readonly ref として宣言すると、ref structreadonly struct の制限の利点と制限が組み合わされます。Declaring a struct as readonly ref combines the benefits and restrictions of ref struct and readonly struct declarations. 読み取り専用スパンによって使用されるメモリは単一のスタック フレームに制限され、読み取り専用スパンによって使用されるメモリは変更できません。The memory used by the readonly span is restricted to a single stack frame, and the memory used by the readonly span can't be modified.

まとめConclusions

値の型を使用すると、割り当て操作の数が最小限になります。Using value types minimizes the number of allocation operations:

  • 値の型の記憶域は、ローカル変数とメソッド引数に割り当てられたスタックです。Storage for value types is stack allocated for local variables and method arguments.
  • 他のオブジェクトのメンバーである値の型に対する記憶域は、別の割り当てとしてではなく、そのオブジェクトの一部として割り当てられます。Storage for value types that are members of other objects is allocated as part of that object, not as a separate allocation.
  • 値の型の戻り値に対する記憶域は、割り当てられたスタックです。Storage for value type return values is stack allocated.

それを同じ状況での参照型と比較します。Contrast that with reference types in those same situations:

  • 参照型の記憶域は、ローカル変数とメソッド引数に割り当てられたヒープです。Storage for reference types are heap allocated for local variables and method arguments. 参照は、スタック上に格納されます。The reference is stored on the stack.
  • 他のオブジェクトのメンバーである参照型に対する記憶域は、ヒープ上に別に割り当てられます。Storage for reference types that are members of other objects are separately allocated on the heap. 参照は親オブジェクトに格納されます。The containing object stores the reference.
  • 参照型の戻り値に対する記憶域は、割り当てられたヒープです。Storage for reference type return values is heap allocated. その記憶域に対する参照は、スタック上に格納されます。The reference to that storage is stored on the stack.

割り当てを最小限に抑えることにはトレードオフが伴います。Minimizing allocations comes with tradeoffs. struct のサイズが参照のサイズより大きいと、コピーするメモリの量が増えます。You copy more memory when the size of the struct is larger than the size of a reference. 通常、参照は 32 ビットまたは 64 ビットであり、ターゲット コンピューターの CPU に応じます。A reference is typically 64 bits or 32 bits, and depends on the target machine CPU.

一般に、これらのトレードオフによるパフォーマンスへの影響は最小限です。These tradeoffs generally have minimal performance impact. ただし、大きい構造体または大きいコレクションでは、パフォーマンスへの影響が大きくなります。However, for large structs or larger collections, the performance impact increases. 影響は、短いループおよびプログラムに対するホット パスで、大きくなる可能性があります。The impact can be large in tight loops and hot paths for programs.

C# 言語の以上の拡張機能は、必要なパフォーマンスの達成においてメモリの割り当てを最小限にすることが大きな要因である、パフォーマンス クリティカルなアルゴリズムのために設計されています。These enhancements to the C# language are designed for performance critical algorithms where minimizing memory allocations is a major factor in achieving the necessary performance. 自分が記述するコードではこれらの機能を頻繁に使用することがないかもしれません。You may find that you don't often use these features in the code you write. ただし、これらの拡張機能は .NET 全体で採用されています。However, these enhancements have been adopted throughout .NET. これらの機能を利用する API が増えれば、自分で作るアプリケーションのパフォーマンスが向上するでしょう。As more and more APIs make use of these features, you'll see the performance of your applications improve.

関連項目See also