🔬
[C#]CollectionsMarshal の解説
.NET 5 で System.Runtime.InteropServices.CollectionsMarshal
というクラスが導入されました。
List<T>
や Dictionary<TKey, TValue>
の少し低レベルなところを触らせてくれます。
List<T>
用
AsSpan
導入時期: .NET 5
List<T>
の内部配列を参照する Span<T>
を取り出します。
List<T>.Add
の前後で使ったりすると危ういので、一時的な用途に限定して使いましょう。
using System.Runtime.InteropServices;
var list = Enumerable.Range(0, 10).ToList();
var span = CollectionsMarshal.AsSpan(list);
span[^1] = -1;
Console.WriteLine(string.Join(", ", list));
// 0, 1, 2, 3, 4, 5, 6, 7, 8, -1
list.Add(100); // 内部配列が再確保される
span[0] = -2; // この span は list の内部配列の参照ではなくなっている
Console.WriteLine(string.Join(", ", list));
// 0, 1, 2, 3, 4, 5, 6, 7, 8, -1, 100
Dictionary<TKey, TValue>
用
GetValueRefOrAddDefault
導入時期: .NET 6
Dictionary<TKey, TValue>
から参照を取り出します。存在しないキーを指定した場合は default
で初期化されます。
メソッドの戻り値で値への参照を返し、キーが存在するかを out
引数で返します。
値の参照を返す都合で、Dictionary<TKey, TValue>.TryGetValue
ではキーが存在するかをメソッドの戻り値で返し、値は out
引数で返す仕様と異なるのに注意。
こちらも値を追加する前後で使ったりすると危ういので、一時的な用途に限定して使いましょう。
using System.Runtime.InteropServices;
var dic = new Dictionary<string, int> {
{ "foo", 1 },
};
bool exists;
ref var foo = ref CollectionsMarshal.GetValueRefOrAddDefault(dic, "foo", out exists);
System.Diagnostics.Debug.Assert(exists);
ref var bar = ref CollectionsMarshal.GetValueRefOrAddDefault(dic, "bar", out exists);
System.Diagnostics.Debug.Assert(!exists);
foo++;
bar++;
Console.WriteLine(string.Join(", ", dic));
// [foo, 2], [bar, 1]
dic.Add("baz", -1);
foo++;
Console.WriteLine(dic["foo"]);
// 3
dic.Add("foobar", -1); // ここで再確保される
foo++; // foo は dic の内部の参照ではない
Console.WriteLine(dic["foo"]);
// 3
GetValueRefOrNullRef
導入時期: .NET 6
Dictionary<TKey, TValue>
から参照を取り出します。存在しないキーを指定した場合は null 参照になります。
null 参照に触れると例外が投げられるので、Unsafe.IsNullRef
を使ってチェックしてあげましょう。
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
var dic = new Dictionary<string, int> {
{ "foo", 1 },
};
ref var foo = ref CollectionsMarshal.GetValueRefOrNullRef(dic, "foo");
System.Diagnostics.Debug.Assert(!Unsafe.IsNullRef(ref foo));
ref var bar = ref CollectionsMarshal.GetValueRefOrNullRef(dic, "bar");
System.Diagnostics.Debug.Assert(Unsafe.IsNullRef(ref bar));
foo++;
Console.WriteLine(string.Join(", ", dic));
// [foo, 2]
try
{
bar++;
}
catch (NullReferenceException e)
{
Console.WriteLine(e.Message);
// Object reference not set to an instance of an object.
}
Discussion