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, b は同一。ReadOnlySpan最適化(参考:【C#】ReadOnlySpan<T>最適化の確認 - てくメモ)
- c の stackalloc は Visual Studio による書き換え推奨が出るが、あえて書くとコレクション式と異なる内容
- d のTは参照型だが、インライン配列が用いられている
// 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