rksoftware

Visual Studio とか C# とかが好きです

C# 11 の新機能を確認「 ref フィールドと scoped ref 」

C# 11 の新機能を確認しています。目次は次の記事です。
rksoftware.hatenablog.com

今回は 「 ref フィールドと scoped ref 」。公式 Learn の記事は次です。

「 ref フィールド 」について

learn.microsoft.com

C# 11 以降では、ref struct に ref フィールドを含めることができます。~ 」から始まる部分です。 ref 構造体のフィールドを ref にできます。

「 scoped ref 」について

learn.microsoft.com

ref の値の範囲を現在のスコープに限定します。

■ ref フィールド確認

次のようなコードが書けます。

ref struct Saitama
{
    public ref string Omiya;
    public Saitama(ref string omiya) => Omiya = ref omiya;
}

このコードは .NET 6 を指定した場合は次のエラーになります。

エラー CS9064 対象のランタイムは ref フィールドをサポートしていません。
エラー CS8936 機能 'ref フィールド' は C# 10.0 では使用できません。言語バージョン 11.0 以上を使用してください。

■ scoped ref 確認

例えば ref を使った次のコードがあったとします。

string saiko1 = "最高";
string saiko2 = "最高";

Saitama saitama = new (ref saiko1);
ref Saitama saitama1 = ref Method1(ref saitama);
Saitama saitama2 = Method2(saitama);
Saitama saitama3 = Method3(ref saiko2);

saitama1.Omiya = "best!";
saitama2.Omiya = "best!!";
saitama3.Omiya = "best!!!";

Console.WriteLine($"{saitama1.Omiya}\n{saitama1.Omiya}\n{saitama2.Omiya}\n{saitama3.Omiya}");
// best!!
// best!!
// best!!
// best!!!

static ref Saitama Method1(ref Saitama saitama)
{
    return ref saitama;
}

static Saitama Method2(Saitama saitama)
{
    return saitama;
}

static Saitama Method3(ref string saiko)
{
    Saitama saitama = new(ref saiko);
    return saitama;
}

ref struct Saitama
{
    public ref string Omiya;
    public Saitama(ref string omiya) => Omiya = ref omiya;
}

ここで、scopred を使って範囲を限定してみます。
すると範囲を限定したので引数に scoped が付いているとその引数を return できなくなります。同じくメソッド内のローカル変数に scoped が付いているとその変数も return できません。

static ref Saitama Method1(scoped ref Saitama saitama)
{
    return ref saitama; // エラー CS9075  現在のメソッドに範囲指定されているため、参照渡し 'saitama' でパラメーターを返すことはできません
}

static Saitama Method2(scoped Saitama saitama)
{
    return saitama;  // エラー CS8352 参照される変数が宣言のスコープ外に公開される可能性があるため、このコンテキストで変数 'scoped Saitama' を使用することはできません
}

static Saitama Method3(ref string saiko)
{
    scoped Saitama saitama = new(ref saiko);
    return saitama; // エラー CS8352  参照される変数が宣言のスコープ外に公開される可能性があるため、このコンテキストで変数 'saitama' を使用することはできません
}

ref struct Saitama
{
    public ref string Omiya;
    public Saitama(ref string omiya) => Omiya = ref omiya;
}

このコードは .NET 6 を指定した場合は次のエラーになります。

エラー CS9064 対象のランタイムは ref フィールドをサポートしていません。
エラー CS8936 機能 'ref フィールド' は C# 10.0 では使用できません。言語バージョン 11.0 以上を使用してください。
エラー CS9075  現在のメソッドに範囲指定されているため、参照渡し 'saitama' でパラメーターを返すことはできません
static ref Saitama Method1(scoped ref Saitama saitama)  // エラー CS8936 機能 'ref フィールド' は C# 10.0 では使用できません。言語バージョン 11.0 以上を使用してください。
{
    return ref saitama; // エラー CS9075  現在のメソッドに範囲指定されているため、参照渡し 'saitama' でパラメーターを返すことはできません
}

static Saitama Method2(scoped Saitama saitama)  // エラー CS8936 機能 'ref フィールド' は C# 10.0 では使用できません。言語バージョン 11.0 以上を使用してください。
{
    return saitama;
}

static Saitama Method3(ref string saiko)
{
    scoped Saitama saitama = new(ref saiko);  // エラー CS8936 機能 'ref フィールド' は C# 10.0 では使用できません。言語バージョン 11.0 以上を使用してください。
    return saitama;
}

ref struct Saitama
{
    public ref string Omiya;    // エラー CS8936 機能 'ref フィールド' は C# 10.0 では使用できません。言語バージョン 11.0 以上を使用してください。
                                // エラー CS9064 対象のランタイムは ref フィールドをサポートしていません。
    public Saitama(ref string omiya) => Omiya = ref omiya;
}

■ 限定の回避を試みる

いくつかのパターンでこの限定を回避しようとしてみましたが、ちゃんとおこられました。鋭いですね。

参照を保持するかもしれない別の構造体を return (プロパティの場合)

Holder 構造体のプロパティにセットして Holder 構造体を return するかもしれないコードです。
※実際にはプロパティには保持しませんが、Method の中のコードだけ見ると保持する可能性があります。

Holder Method(scoped Saitama saitama)
{
    Holder holder = new() { Saitama = saitama };
    return holder;  // エラー CS8352 参照される変数が宣言のスコープ外に公開される可能性があるため、このコンテキストで変数 'holder' を使用することはできません
}

ref struct Saitama { }

ref struct Holder
{
    public Saitama Saitama { get => default; set {; } }
}

参照を保持するかもしれない別の構造体を return (メソッドの場合)

Holder 構造体の Method に引数で渡して Holder 構造体を return するかもしれないコードです。
※実際には Holder 構造体内で保持しませんが、Method の中のコードだけ見ると保持する可能性があります。

Holder Method(scoped Saitama saitama)
{
    Holder holder = new();
    holder.Add(saitama);    // エラー CS8350 パラメーター 'saitama' によって参照される変数が宣言のスコープ外に公開される可能性があるため、'Holder.Add(Saitama)' に対してこの引数の組み合わせは許可されません
                            // エラー CS8352 参照される変数が宣言のスコープ外に公開される可能性があるため、このコンテキストで変数 'scoped Saitama' を使用することはできません
    return holder;
}

ref struct Saitama { }

ref struct Holder
{
    public void Add(Saitama saitama) { }
}

参照を保持するかもしれない別の構造体を return (別の構造体を return するメソッドの場合)

Holder 構造体を内部で生成している Method2 に引数で渡して Holder 構造体を return するコードです。Method の中のコードを見ただけでは、Method2 の中で Holder 構造体に参照を保持する可能性が否定できないパターンです。 ※実際には Holder 構造体内で保持しませんが、Method の中のコードだけ見ると保持する可能性があります。

Holder Method(scoped Saitama saitama)
{
    Holder holder = Method2(saitama);
    return holder;  // エラー CS8352 参照される変数が宣言のスコープ外に公開される可能性があるため、このコンテキストで変数 'holder' を使用することはできません
}

Holder Method2(Saitama saitama) => default;

ref struct Saitama { }

ref struct Holder { }

この時、Holder が ref struct ではなく、ただの struct や class であればエラーになりません。また、Method2 が return した Holder をさらに Method が return しなければそれもエラーになりません。

参照を保持するかもしれない別の構造体を ref で受け取っている場合

Holder 構造体を return するのではなく ref 引数で受け取っているパターンです。 ※実際には Holder 構造体内で保持しませんが、Method の中のコードだけ見ると保持する可能性があります。

void Method(scoped Saitama saitama, ref Holder holder)
{
    holder = Method2(saitama);  // エラー CS8347 パラメーター 'saitama' によって参照される変数が宣言のスコープ外に公開される可能性があるため、このコンテキストで 'Method2(Saitama)' の結果を使用することはできません
                                // エラー CS8352 参照される変数が宣言のスコープ外に公開される可能性があるため、このコンテキストで変数 'scoped Saitama' を使用することはできません
}

Holder Method2(Saitama saitama) => default;

ref struct Saitama { }

ref struct Holder { }

どのパターンもエラーになってくれました。安心ですね。

■ 覚えておきましょう

この新機能は使う機会のない人はあまり使わないと思います。使うべき時に忘れていないようしっかり覚えておきましょう。