次の方法で共有


読み取り専用インスタンス メンバー

メモ

この記事は機能仕様についてです。 仕様は、機能の設計ドキュメントとして使用できます。 これには、提案された仕様の変更および機能の設計と開発時に必要な情報が含まれます。 これらの記事は、提案された仕様の変更が決定され、現在の ECMA 仕様に組み込まれるまで公開されます。

機能の仕様と行われた実装では、いくつかの違いがあることがあります。 これらの違いは、関連する言語設計ミーティング (LDM) メモに取り上げられています。

機能仕様を C# 言語標準に導入するプロセスの詳細については、仕様に関する記事を参照してください。

チャンピオンの課題: <https://github.com/dotnet/csharplang/issues/1710>

まとめ

構造体の個々のインスタンス メンバーが状態を変更しないように指定する方法を示します。これは、readonly struct がすべてのインスタンス メンバーによる状態の変更を許可しないのと同様の方法です。

注目すべきこととして、読み取り専用インスタンス メンバーは純粋インスタンス メンバーはです。 pure インスタンス メンバーは、状態が一切変更されないことを担保します。 readonly インスタンス メンバーが担保するのは、インスタンスの状態が変更されないことのみです。

readonly struct 上のすべてのインスタンス メンバーは、暗黙的に読み取り専用インスタンス メンバーとみなされます。非読み取り専用構造体で宣言された明示的な読み取り専用インスタンス メンバーも同様に動作します。 たとえば、それ自体が読み取り専用ではなかったインスタンス メンバー (現在のインスタンス上またはインスタンスのフィールド上) を呼び出した場合でも、非表示のコピーが作成されます。

目的

C# 8.0 より前では、ユーザーは readonly struct 型を作成できました。この型では、コンパイラによってすべてのフィールドが読み取り専用であることが強制されます (つまり、インスタンス メンバーによって状態が変更されることはありません)。 しかし、アクセス可能なフィールドを公開している API や、状態を変更するメンバーと変更しないメンバーが混在する API を既に使用している場合があります。 そのような場合、型を readonly とマークすることはできません (既存のコードの破壊的変更となるため)。

これは通常、大きな影響はありませんが、in パラメーターを使用する場合は例外です。 読み取り専用ではない構造体に in パラメーターを使用している場合、コンパイラは呼び出しによって内部状態が変更されないことを担保できないため、インスタンス メンバーの呼び出しごとにパラメーターのコピーを作成します。 これにより、構造体を値渡しした場合と比べて、多数のコピーが作成され、全体的なパフォーマンスが低下する可能性があります。 例については、sharplab でこのコードを参照してください

非表示のコピーが発生する可能性があるその他のシナリオには、フィールド 、リテラルなどがあります。 将来的にサポートされた場合、blittable定数 は同じ状況になります。つまり、構造体が readonlyマークされていない場合、現時点では(インスタンスメンバーの呼び出し時に) 完全なコピーが必要です。

設計

ユーザーは、インスタンス メンバー自体を readonly とし、インスタンスの状態を変更しないように指定できます (もちろん、適切な検証はコンパイラによって行われます)。 次に例を示します。

public struct Vector2
{
    public float x;
    public float y;

    public readonly float GetLengthReadonly()
    {
        return MathF.Sqrt(LengthSquared);
    }

    public float GetLength()
    {
        return MathF.Sqrt(LengthSquared);
    }

    public readonly float GetLengthIllegal()
    {
        var tmp = MathF.Sqrt(LengthSquared);

        x = tmp;    // Compiler error, cannot write x
        y = tmp;    // Compiler error, cannot write y

        return tmp;
    }

    public readonly float LengthSquared
    {
        get
        {
            return (x * x) +
                   (y * y);
        }
    }
}

public static class MyClass
{
    public static float ExistingBehavior(in Vector2 vector)
    {
        // This code causes a hidden copy, the compiler effectively emits:
        //    var tmpVector = vector;
        //    return tmpVector.GetLength();
        //
        // This is done because the compiler doesn't know that `GetLength()`
        // won't mutate `vector`.

        return vector.GetLength();
    }

    public static float ReadonlyBehavior(in Vector2 vector)
    {
        // This code is emitted exactly as listed. There are no hidden
        // copies as the `readonly` modifier indicates that the method
        // won't mutate `vector`.

        return vector.GetLengthReadonly();
    }
}

プロパティ アクセサーに「読み取り専用」を適用することで、アクセサー内で this が変更されないことを示すことができます。 次の例では、アクセサーがメンバー フィールドの状態を変更しますが、そのメンバー フィールドの値は変更しないため、readonly セッターを使用しています。

public readonly int Prop1
{
    get
    {
        return this._store["Prop1"];
    }
    set
    {
        this._store["Prop1"] = value;
    }
}

プロパティ構文に readonly を適用すると、すべてのアクセサーが readonly になります。

public readonly int Prop2
{
    get
    {
        return this._store["Prop2"];
    }
    set
    {
        this._store["Prop2"] = value;
    }
}

readonly は、コンテナー型を変更しないアクセサーにのみ適用できます。

public int Prop3
{
    readonly get
    {
        return this._prop3;
    }
    set
    {
        this._prop3 = value;
    }
}

読み取り専用は、一部の自動実装プロパティに適用できますが、実質的な効果はありません。 コンパイラは、readonly キーワードの有無にかかわらず、自動実装されたすべてのゲッターを readonly ゲッターとして扱います。

// Allowed
public readonly int Prop4 { get; }
public int Prop5 { readonly get; set; }

// Not allowed
public int Prop6 { readonly get; }
public readonly int Prop7 { get; set; }
public int Prop8 { get; readonly set; }

読み取り専用は、手動で実装されたイベントには適用できますが、フィールド形式のイベントには適用できません。 読み取り専用を個々のイベント アクセサー (追加/削除) に適用することはできません。

// Allowed
public readonly event Action<EventArgs> Event1
{
    add { }
    remove { }
}

// Not allowed
public readonly event Action<EventArgs> Event2;
public event Action<EventArgs> Event3
{
    readonly add { }
    readonly remove { }
}
public static readonly event Event4
{
    add { }
    remove { }
}

その他の構文例:

  • 式形式メンバー: public readonly float ExpressionBodiedMember => (x * x) + (y * y);
  • ジェネリック制約: public readonly void GenericMethod<T>(T value) where T : struct { }

コンパイラは通常どおりインスタンス メンバーを生成し、さらに、そのメンバーが状態を変更しないことを示すコンパイラ認識用属性を付与します。 これにより、非表示の this パラメーターが ref T ではなく in T になります。

その結果、コンパイラがコピーを作成する必要がなくなり、安全にそのインスタンス メソッドを呼び出すことができます。

制約:

  • readonly 修飾子は、static メソッド、コンストラクター、またはデストラクターには適用できません。
  • readonly 修飾子は、デリゲートには適用できません。
  • readonly 修飾子は、クラスまたはインターフェイスのメンバーには適用できません。

デメリット

現在、readonly struct メソッドと同じ欠点があります。 特定のコードでは、依然として非表示のコピーが作成される可能性があります。

メモ

属性や他のキーワードを使用することもできます。

この提案は、functional purityconstant expressionsに多少関連しており、これらの既存の提案の一部として位置づけることができます。