てくメモ

trivial な notes

【C#】コレクション式 : ILを見てどういう中身か掴む(Span, ReadOnlySpan, 配列, List)

C# 12 で導入されたコレクション式について、従来型の記述などとILを簡単に見比べておくもの。(.NET 8.0 リリース時点、ILSpy使用)

コレクション式についての参考リンク
コレクション式 (コレクション リテラル) - C# | Microsoft Learn
コレクション式 - C# によるプログラミング入門 | ++C++; // 未確認飛行 C


最初に、見比べたうえでの所感: 使えるなら全部コレクション式でよいと感じた。


目次


Span

  • コレクション式 (a)
  • 配列初期化子 (b)
  • stackalloc (c)
  • T が参照型(文字列)のコレクション式 (d)
public string Span()
{
    Span<int> a = [1, 2, 3];
    Span<int> b = new int[] { 1, 2, 3 };
    Span<int> c = stackalloc int[] { 1, 2, 3 }; // Visual Studio によるコレクション式推奨あり

    Span<string> d = ["a", "b", "c"];

    // 最適化による消去防止
    return $"{a.ToString()}, {b.ToString()}, {c.ToString()}, {d.ToString()}";
}


【!】以下では簡潔のため、メソッドの中身のみを記載し、また、最適化消去防止用の箇所を省略する。


  • a はインライン配列(C# 12~)が用いられている
  • b は配列が用いられている
  • c の stackalloc は、Visual Studio による書き換え推奨が出るが、あえて書くとコレクション式と異なる内容
  • d のTは参照型だが、値型である場合と同じようにインライン配列が用いられている
// `// a`のようなコメントは、筆者が目印で付したもの
// a
IL_0000: ldloca.s 4
IL_0002: initobj valuetype '<>y__InlineArray3`1'<int32>
IL_0008: ldloca.s 4
IL_000a: ldc.i4.0
IL_000b: call !!1& '<PrivateImplementationDetails>'::InlineArrayElementRef<valuetype '<>y__InlineArray3`1'<int32>, int32>(!!0&, int32)
IL_0010: ldc.i4.1
IL_0011: stind.i4
IL_0012: ldloca.s 4
IL_0014: ldc.i4.1
IL_0015: call !!1& '<PrivateImplementationDetails>'::InlineArrayElementRef<valuetype '<>y__InlineArray3`1'<int32>, int32>(!!0&, int32)
IL_001a: ldc.i4.2
IL_001b: stind.i4
IL_001c: ldloca.s 4
IL_001e: ldc.i4.2
IL_001f: call !!1& '<PrivateImplementationDetails>'::InlineArrayElementRef<valuetype '<>y__InlineArray3`1'<int32>, int32>(!!0&, int32)
IL_0024: ldc.i4.3
IL_0025: stind.i4
IL_0026: ldloca.s 4
IL_0028: ldc.i4.3
IL_0029: call valuetype [System.Runtime]System.Span`1<!!1> '<PrivateImplementationDetails>'::InlineArrayAsSpan<valuetype '<>y__InlineArray3`1'<int32>, int32>(!!0&, int32)
IL_002e: stloc.0

// b
IL_002f: ldc.i4.3
IL_0030: newarr [System.Runtime]System.Int32
IL_0035: dup
IL_0036: ldtoken field valuetype '<PrivateImplementationDetails>'/'__StaticArrayInitTypeSize=12' '<PrivateImplementationDetails>'::'4636993D3E1DA4E9D6B8F87B79E8F7C6D018580D52661950EABC3845C5897A4D'
IL_003b: call void [System.Runtime]System.Runtime.CompilerServices.RuntimeHelpers::InitializeArray(class [System.Runtime]System.Array, valuetype [System.Runtime]System.RuntimeFieldHandle)
IL_0040: call valuetype [System.Runtime]System.Span`1<!0> valuetype [System.Runtime]System.Span`1<int32>::op_Implicit(!0[])
IL_0045: stloc.1

// c
IL_0046: ldc.i4.s 12
IL_0048: conv.u
IL_0049: localloc
IL_004b: dup
IL_004c: ldc.i4.1
IL_004d: stind.i4
IL_004e: dup
IL_004f: ldc.i4.4
IL_0050: add
IL_0051: ldc.i4.2
IL_0052: stind.i4
IL_0053: dup
IL_0054: ldc.i4.2
IL_0055: conv.i
IL_0056: ldc.i4.4
IL_0057: mul
IL_0058: add
IL_0059: ldc.i4.3
IL_005a: stind.i4
IL_005b: ldc.i4.3
IL_005c: newobj instance void valuetype [System.Runtime]System.Span`1<int32>::.ctor(void*, int32)
IL_0061: stloc.2

// d
IL_0062: ldloca.s 5
IL_0064: initobj valuetype '<>y__InlineArray3`1'<string>
IL_006a: ldloca.s 5
IL_006c: ldc.i4.0
IL_006d: call !!1& '<PrivateImplementationDetails>'::InlineArrayElementRef<valuetype '<>y__InlineArray3`1'<string>, string>(!!0&, int32)
IL_0072: ldstr "a"
IL_0077: stind.ref
IL_0078: ldloca.s 5
IL_007a: ldc.i4.1
IL_007b: call !!1& '<PrivateImplementationDetails>'::InlineArrayElementRef<valuetype '<>y__InlineArray3`1'<string>, string>(!!0&, int32)
IL_0080: ldstr "b"
IL_0085: stind.ref
IL_0086: ldloca.s 5
IL_0088: ldc.i4.2
IL_0089: call !!1& '<PrivateImplementationDetails>'::InlineArrayElementRef<valuetype '<>y__InlineArray3`1'<string>, string>(!!0&, int32)
IL_008e: ldstr "c"
IL_0093: stind.ref
IL_0094: ldloca.s 5
IL_0096: ldc.i4.3
IL_0097: call valuetype [System.Runtime]System.Span`1<!!1> '<PrivateImplementationDetails>'::InlineArrayAsSpan<valuetype '<>y__InlineArray3`1'<string>, string>(!!0&, int32)
IL_009c: stloc.3

ReadOnlySpan

  • コレクション式 (a)
  • 配列初期化子 (b)
  • stackalloc (c)
  • T が参照型(文字列)のコレクション式 (d)
ReadOnlySpan<int> a = [1, 2, 3];
ReadOnlySpan<int> b = new int[] { 1, 2, 3 };
ReadOnlySpan<int> c = stackalloc int[] { 1, 2, 3 }; // Visual Studio によるコレクション式推奨あり

ReadOnlySpan<string> d = ["a", "b", "c"];
// a
IL_0000: ldtoken field valuetype '<PrivateImplementationDetails>'/'__StaticArrayInitTypeSize=12_Align=4' '<PrivateImplementationDetails>'::'4636993D3E1DA4E9D6B8F87B79E8F7C6D018580D52661950EABC3845C5897A4D4'
IL_0005: call valuetype [System.Runtime]System.ReadOnlySpan`1<!!0> [System.Runtime]System.Runtime.CompilerServices.RuntimeHelpers::CreateSpan<int32>(valuetype [System.Runtime]System.RuntimeFieldHandle)
IL_000a: stloc.0

// b
IL_000b: ldtoken field valuetype '<PrivateImplementationDetails>'/'__StaticArrayInitTypeSize=12_Align=4' '<PrivateImplementationDetails>'::'4636993D3E1DA4E9D6B8F87B79E8F7C6D018580D52661950EABC3845C5897A4D4'
IL_0010: call valuetype [System.Runtime]System.ReadOnlySpan`1<!!0> [System.Runtime]System.Runtime.CompilerServices.RuntimeHelpers::CreateSpan<int32>(valuetype [System.Runtime]System.RuntimeFieldHandle)
IL_0015: stloc.1

// c
IL_0016: ldc.i4.s 12
IL_0018: conv.u
IL_0019: localloc
IL_001b: dup
IL_001c: ldc.i4.1
IL_001d: stind.i4
IL_001e: dup
IL_001f: ldc.i4.4
IL_0020: add
IL_0021: ldc.i4.2
IL_0022: stind.i4
IL_0023: dup
IL_0024: ldc.i4.2
IL_0025: conv.i
IL_0026: ldc.i4.4
IL_0027: mul
IL_0028: add
IL_0029: ldc.i4.3
IL_002a: stind.i4
IL_002b: ldc.i4.3
IL_002c: newobj instance void valuetype [System.Runtime]System.Span`1<int32>::.ctor(void*, int32)
IL_0031: call valuetype [System.Runtime]System.ReadOnlySpan`1<!0> valuetype [System.Runtime]System.Span`1<int32>::op_Implicit(valuetype [System.Runtime]System.Span`1<!0>)
IL_0036: stloc.2

// d
IL_0037: ldloca.s 4
IL_0039: initobj valuetype '<>y__InlineArray3`1'<string>
IL_003f: ldloca.s 4
IL_0041: ldc.i4.0
IL_0042: call !!1& '<PrivateImplementationDetails>'::InlineArrayElementRef<valuetype '<>y__InlineArray3`1'<string>, string>(!!0&, int32)
IL_0047: ldstr "a"
IL_004c: stind.ref
IL_004d: ldloca.s 4
IL_004f: ldc.i4.1
IL_0050: call !!1& '<PrivateImplementationDetails>'::InlineArrayElementRef<valuetype '<>y__InlineArray3`1'<string>, string>(!!0&, int32)
IL_0055: ldstr "b"
IL_005a: stind.ref
IL_005b: ldloca.s 4
IL_005d: ldc.i4.2
IL_005e: call !!1& '<PrivateImplementationDetails>'::InlineArrayElementRef<valuetype '<>y__InlineArray3`1'<string>, string>(!!0&, int32)
IL_0063: ldstr "c"
IL_0068: stind.ref
IL_0069: ldloca.s 4
IL_006b: ldc.i4.3
IL_006c: call valuetype [System.Runtime]System.ReadOnlySpan`1<!!1> '<PrivateImplementationDetails>'::InlineArrayAsReadOnlySpan<valuetype '<>y__InlineArray3`1'<string>, string>(!!0&, int32)
IL_0071: stloc.3

配列

  • コレクション式 (a)
  • 配列初期化子 (b)
int[] a = [1, 2, 3];
int[] b = new int[] { 1, 2, 3 }; // Visual Studio によるコレクション式推奨あり
  • a, b 同一
// a
IL_0000: ldc.i4.3
IL_0001: newarr [System.Runtime]System.Int32
IL_0006: dup
IL_0007: ldtoken field valuetype '<PrivateImplementationDetails>'/'__StaticArrayInitTypeSize=12' '<PrivateImplementationDetails>'::'4636993D3E1DA4E9D6B8F87B79E8F7C6D018580D52661950EABC3845C5897A4D'
IL_000c: call void [System.Runtime]System.Runtime.CompilerServices.RuntimeHelpers::InitializeArray(class [System.Runtime]System.Array, valuetype [System.Runtime]System.RuntimeFieldHandle)
IL_0011: stloc.0

// b
IL_0012: ldc.i4.3
IL_0013: newarr [System.Runtime]System.Int32
IL_0018: dup
IL_0019: ldtoken field valuetype '<PrivateImplementationDetails>'/'__StaticArrayInitTypeSize=12' '<PrivateImplementationDetails>'::'4636993D3E1DA4E9D6B8F87B79E8F7C6D018580D52661950EABC3845C5897A4D'
IL_001e: call void [System.Runtime]System.Runtime.CompilerServices.RuntimeHelpers::InitializeArray(class [System.Runtime]System.Array, valuetype [System.Runtime]System.RuntimeFieldHandle)
IL_0023: stloc.1

List

  • コレクション式 (a)、
  • コレクション初期化子 (b)。
List<int> a = [1, 2, 3];
List<int> b = new() { 1, 2, 3 }; // Visual Studio によるコレクション式推奨あり
  • a は中身の Span を引き抜いて直接値を叩き込んでいる。
  • b は Visual Studio による書き換え推奨が出るが、あえて書くとコレクション式と異なり、通常のコレクション初期化子の挙動(Addメソッドを呼ぶ)となっている
// a
IL_0000: newobj instance void class [System.Collections]System.Collections.Generic.List`1<int32>::.ctor()
IL_0005: dup
IL_0006: ldc.i4.3
IL_0007: call void [System.Runtime.InteropServices]System.Runtime.InteropServices.CollectionsMarshal::SetCount<int32>(class [System.Collections]System.Collections.Generic.List`1<!!0>, int32)
IL_000c: dup
IL_000d: call valuetype [System.Runtime]System.Span`1<!!0> [System.Runtime.InteropServices]System.Runtime.InteropServices.CollectionsMarshal::AsSpan<int32>(class [System.Collections]System.Collections.Generic.List`1<!!0>)
IL_0012: stloc.2
IL_0013: ldc.i4.0
IL_0014: stloc.3
IL_0015: ldloca.s 2
IL_0017: ldloc.3
IL_0018: call instance !0& valuetype [System.Runtime]System.Span`1<int32>::get_Item(int32)
IL_001d: ldc.i4.1
IL_001e: stind.i4
IL_001f: ldloc.3
IL_0020: ldc.i4.1
IL_0021: add
IL_0022: stloc.3
IL_0023: ldloca.s 2
IL_0025: ldloc.3
IL_0026: call instance !0& valuetype [System.Runtime]System.Span`1<int32>::get_Item(int32)
IL_002b: ldc.i4.2
IL_002c: stind.i4
IL_002d: ldloc.3
IL_002e: ldc.i4.1
IL_002f: add
IL_0030: stloc.3
IL_0031: ldloca.s 2
IL_0033: ldloc.3
IL_0034: call instance !0& valuetype [System.Runtime]System.Span`1<int32>::get_Item(int32)
IL_0039: ldc.i4.3
IL_003a: stind.i4
IL_003b: ldloc.3
IL_003c: ldc.i4.1
IL_003d: add
IL_003e: stloc.3
IL_003f: stloc.0

// b
IL_0040: newobj instance void class [System.Collections]System.Collections.Generic.List`1<int32>::.ctor()
IL_0045: dup
IL_0046: ldc.i4.1
IL_0047: callvirt instance void class [System.Collections]System.Collections.Generic.List`1<int32>::Add(!0)
IL_004c: dup
IL_004d: ldc.i4.2
IL_004e: callvirt instance void class [System.Collections]System.Collections.Generic.List`1<int32>::Add(!0)
IL_0053: dup
IL_0054: ldc.i4.3
IL_0055: callvirt instance void class [System.Collections]System.Collections.Generic.List`1<int32>::Add(!0)
IL_005a: stloc.1