🔬
[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