C# 11 の新機能を確認しています。目次は次の記事です。
rksoftware.hatenablog.com
今回は 「 ref フィールドと scoped ref 」。公式 Learn の記事は次です。
「 ref フィールド 」について
「 C# 11 以降では、ref struct に ref フィールドを含めることができます。~ 」から始まる部分です。 ref 構造体のフィールドを ref にできます。
「 scoped ref 」について
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 { }
どのパターンもエラーになってくれました。安心ですね。
■ 覚えておきましょう
この新機能は使う機会のない人はあまり使わないと思います。使うべき時に忘れていないようしっかり覚えておきましょう。