ref fields
Closed this issue · 11 comments
https://ufcpp.net/blog/2022/2/ref-field/
csharplang/pull/6338
csharplang/issues/6337
- 記法としてはフィールドの型に ref 付けるだけ
- ref struct にだけ持てる
- これの安全性を保障するために、ref struct に対する escape analysis にちょっと手が入ってる
- scoped (↑ の修正前と同じ挙動を得るために使う)
- 引数
- ローカル変数に付けれる
- 構造体の this と out 引数はデフォルトで scoped だっけ
- foreach と using の中の変数にも
- readonly ref readonly T
- オブジェクト初期化子で ref 代入
- 実例
- (BCL 内部の話だけど)
Span<T>
が普通の ref struct になった - TypedReference
- 多値 ref 戻り値
- (BCL 内部の話だけど)
TypedReference の通常 ref struct 化、__makeref
退役- ほんとに .NET 7 でやる?それとも 8?
- UnscopedRef 属性
- FixedArray みたいなの
- C# 10 ルールで解析するか、11 ルールで解析するかをアセンブリ単位で属性で指定
- ref struct の ref field は持てなくなっちゃった…
- 内部的には scoped は ScopedRef 属性っぽい
- unsafe コンテキストではエラーじゃなくて警告になるっぽい
↓は結局いままでのまま。
TypedReferenc m() => default;
今 scoped ref しかない(11ではそれしか対応しない判断した)けど、今後 ref scoped もあり得るらしい。
もしや…
readonly scoped ref readonly scoped Span ある?
ref 解析、unsafe コンテキストではエラーじゃなくて警告だけにするってやつ、ちゃんと入ってた。
static ref int SafeRef()
{
int i = 1;
return ref i; // エラー
}
unsafe static ref int UnsafeRef()
{
int i = 1;
return ref i; // 警告だけに緩和。この例はマジでダメなやつ。
}
これの作業の一環でやったらしいんだけど、参照型のアドレスを &
で取れるようになってるっぽい?
unsafe
{
object o = new();
object* ptr = &o;
}
いつの間に…
なぜ unsafe ref 解析に混ぜてやってる?
csharplang/issues/6476
.NET 8 時点、scoped が付いてる BCL のメソッド、5個しかなかった。
MemoryMarshal CreateSpan reference
MemoryMarshal CreateReadOnlySpan reference
DefaultInterpolatedStringHandler AppendFormatted value
DefaultInterpolatedStringHandler AppendFormatted value
Unsafe AsRef source
しかも MemoryMarshal と Unsafe のは「本来 scoped が付いてたらまずい」「unsafe な手段でコンパイラーに嘘ついてる」メソッド。
実質 DefaultInterpolatedStringHandler AppendFormatted だけが scoped。
static class Util
{
// こっちは OK
public static void AppendAbc(this ref DefaultInterpolatedStringHandler x)
{
x.AppendFormatted(stackalloc char[3] { 'a', 'b', 'c' });
}
// こっちはダメ。
// stackalloc したものが x に伝搬する可能性を懸念。
public static void AppendAbc(this ref Handler x)
{
x.AppendFormatted(stackalloc char[3] { 'a', 'b', 'c' });
}
}
// DefaultInterpolatedStringHandler と同じシグネチャで、scoped だけ取る。
ref struct Handler
{
public void AppendFormatted(ReadOnlySpan<char> span) { }
}
using System.Diagnostics.CodeAnalysis;
ref struct RefInt(ref int target)
{
public ref int Target = ref target;
// 参照がメソッドの外に漏れる想定。
public void Reassign([UnscopedRef] ref int newTarget)
{
Target = ref newTarget;
}
// 参照がメソッドの外に漏れない想定。
public void AddTo(ref int target)
{
target += Target;
}
}
class RefIntExample
{
public static RefInt M1()
{
int x = 0;
return new RefInt(ref x); // ダメ。x の参照が RefInt.Target 越しで外に漏れる。
}
public static RefInt M2()
{
RefInt r = default; // default だと何も参照しないので、
return r; // 外に渡せる。
}
public static RefInt M3()
{
int x = 0;
RefInt r = default;
r.Reassign(ref x); // ダメ
return r; // ここは OK
}
public static RefInt M4()
{
int x = 0;
RefInt r = default;
r.AddTo(ref x);
return r;
}
}
ref T はデフォルトが scoped だけど、Span (ref 構造体)はデフォルトが unscoped。
(ref T のデフォルトは return-only みたい。特殊。)
ref struct RefSpan(Span<int> target)
{
public Span<int> Target = target;
// Span (実質は参照)がメソッドの外に漏れる想定。
public void Reassign(Span<int> newTarget)
{
Target = newTarget;
}
// Span (実質は参照)がメソッドの外に漏れない想定。
public void AddTo(scoped Span<int> target)
{
var len = int.Min(target.Length, Target.Length);
for (var i = 0; i < len; i++) target[i] += Target[i];
}
}
class RefIntExample
{
public static RefSpan M1()
{
Span<int> x = stackalloc int[1];
return new RefSpan(x); // ダメ。x (が参照してる stackalloc)が RefInt.Target 越しで外に漏れる。
}
public static RefSpan M2()
{
RefSpan r = default; // default だと何も参照しないので、
return r; // 外に渡せる。
}
public static RefSpan M3()
{
Span<int> x = stackalloc int[1];
RefSpan r = default;
r.Reassign(x); // ダメ
return r; // ここは OK
}
public static RefSpan M4()
{
Span<int> x = stackalloc int[1];
RefSpan r = default;
r.AddTo(x);
return r;
}
}
ref T のコンストラクター引数は変。
ref struct A
{
public ref int X;
// コンストラクター引数は Unscoped 扱い(特殊)。
// コンストラクターとその他のメソッドでそろってない。
public A(ref int x) => X = ref x;
public void M([UnscopedRef] ref int x) => X = ref x;
}
ref struct B
{
public Span<int> X;
// コンストラクターとその他のメソッドでそろってる。
public B(Span<int> x) => X = x;
public void M(Span<int> x) => X = x;
}