Open10
隠れたメモリアロケーションに注意
ピン留めされたアイテム

関連リンク

もととなるコード
AI に要求を伝えて書いてもらったコード
using System;
using System.Runtime.InteropServices;
static class C
{
static ReadOnlySpan<byte> GetBytes<T>(T value) where T : unmanaged
{
if (typeof(T) == typeof(bool)) return BitConverter.GetBytes( (bool)(object)value);
if (typeof(T) == typeof(char)) return BitConverter.GetBytes( (char)(object)value);
if (typeof(T) == typeof(double)) return BitConverter.GetBytes((double)(object)value);
if (typeof(T) == typeof(short)) return BitConverter.GetBytes( (short)(object)value);
if (typeof(T) == typeof(int)) return BitConverter.GetBytes( (int)(object)value);
if (typeof(T) == typeof(long)) return BitConverter.GetBytes( (long)(object)value);
if (typeof(T) == typeof(float)) return BitConverter.GetBytes( (float)(object)value);
if (typeof(T) == typeof(ushort)) return BitConverter.GetBytes((ushort)(object)value);
if (typeof(T) == typeof(uint)) return BitConverter.GetBytes( (uint)(object)value);
if (typeof(T) == typeof(ulong)) return BitConverter.GetBytes( (ulong)(object)value);
return MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref value, 1));
}
}

以下のいくつかの要因が重なり、なんとなく “くさい” コードに思える
- 🤔 二重キャスト。e.g.
(double)(object)
- 🤔 型の比較のため、
typeof
が重複してあらわれている。e.g.typeof(T) == typeof(double)
- 🤔 同じ型名が一行で重複してあらわれている。e.g.
typeof(double)
(double)(object)value
- 🤔
if
文の意図がわかりにくい。

コンパイラによって解釈されたコード
おおむね元の雰囲気を色濃く残した感じのコードとなる。
折りたたみ
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
internal static class C
{
private static ReadOnlySpan<byte> GetBytes<[IsUnmanaged] T>(T value) where T : struct
{
if (typeof(T) == typeof(bool))
{
return BitConverter.GetBytes((bool)(object)value);
}
if (typeof(T) == typeof(char))
{
return BitConverter.GetBytes((char)(object)value);
}
if (typeof(T) == typeof(double))
{
return BitConverter.GetBytes((double)(object)value);
}
if (typeof(T) == typeof(short))
{
return BitConverter.GetBytes((short)(object)value);
}
if (typeof(T) == typeof(int))
{
return BitConverter.GetBytes((int)(object)value);
}
if (typeof(T) == typeof(long))
{
return BitConverter.GetBytes((long)(object)value);
}
if (typeof(T) == typeof(float))
{
return BitConverter.GetBytes((float)(object)value);
}
if (typeof(T) == typeof(ushort))
{
return BitConverter.GetBytes((ushort)(object)value);
}
if (typeof(T) == typeof(uint))
{
return BitConverter.GetBytes((uint)(object)value);
}
if (typeof(T) == typeof(ulong))
{
return BitConverter.GetBytes((ulong)(object)value);
}
return MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref value, 1));
}
}

改善案
AI に不満点を伝えて改善してもらったコード
using System;
using System.Runtime.InteropServices;
static class C
{
static ReadOnlySpan<byte> GetBytes<T>(T value) where T : unmanaged
{
return value switch
{
bool v => BitConverter.GetBytes(v),
char v => BitConverter.GetBytes(v),
double v => BitConverter.GetBytes(v),
short v => BitConverter.GetBytes(v),
int v => BitConverter.GetBytes(v),
long v => BitConverter.GetBytes(v),
float v => BitConverter.GetBytes(v),
ushort v => BitConverter.GetBytes(v),
uint v => BitConverter.GetBytes(v),
ulong v => BitConverter.GetBytes(v),
_ => MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref value, 1))
};
}
}

ぱっと見でよくなったように思える
- 😄
switch
式とパターンマッチングでなんだかエレガントな感じ! - 😄 コードの重複が減って見通しが良くなった!
- 😄 コードの意図が明瞭になった!

コンパイラによって解釈された改善案のコード
なにやら以前に増して object
があらわれている。object obj = value
折りたたみ
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
internal static class C
{
private static ReadOnlySpan<byte> GetBytes<[IsUnmanaged] T>(T value) where T : struct
{
Span<byte> span;
if (value is bool)
{
object obj = value;
bool value2 = (bool)((obj is bool) ? obj : null);
span = BitConverter.GetBytes(value2);
}
else if (value is char)
{
object obj2 = value;
char value3 = (char)((obj2 is char) ? obj2 : null);
span = BitConverter.GetBytes(value3);
}
else if (value is double)
{
object obj3 = value;
double value4 = (double)((obj3 is double) ? obj3 : null);
span = BitConverter.GetBytes(value4);
}
else if (value is short)
{
object obj4 = value;
short value5 = (short)((obj4 is short) ? obj4 : null);
span = BitConverter.GetBytes(value5);
}
else if (value is int)
{
object obj5 = value;
int value6 = (int)((obj5 is int) ? obj5 : null);
span = BitConverter.GetBytes(value6);
}
else if (value is long)
{
object obj6 = value;
long value7 = (long)((obj6 is long) ? obj6 : null);
span = BitConverter.GetBytes(value7);
}
else if (value is float)
{
object obj7 = value;
float value8 = (float)((obj7 is float) ? obj7 : null);
span = BitConverter.GetBytes(value8);
}
else if (value is ushort)
{
object obj8 = value;
ushort value9 = (ushort)((obj8 is ushort) ? obj8 : null);
span = BitConverter.GetBytes(value9);
}
else if (value is uint)
{
object obj9 = value;
uint value10 = (uint)((obj9 is uint) ? obj9 : null);
span = BitConverter.GetBytes(value10);
}
else if (value is ulong)
{
object obj10 = value;
ulong value11 = (ulong)((obj10 is ulong) ? obj10 : null);
span = BitConverter.GetBytes(value11);
}
else
{
span = MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref value, 1));
}
return span;
}
}

IL
なにやら boxing/unboxing されている。box !!T
、unbox.any
折りたたみ(一部)
IL_0000: ldarg.0
IL_0001: box !!T
IL_0006: isinst [System.Runtime]System.Boolean
IL_000b: brfalse.s IL_0023
IL_000d: ldarg.0
IL_000e: box !!T
IL_0013: isinst [System.Runtime]System.Boolean
IL_0018: unbox.any [System.Runtime]System.Boolean
IL_001d: stloc.0
// sequence point: hidden
IL_001e: br IL_0167
IL_0023: ldarg.0
IL_0024: box !!T
IL_0029: isinst [System.Runtime]System.Char
IL_002e: brfalse.s IL_0046
IL_0030: ldarg.0
IL_0031: box !!T
IL_0036: isinst [System.Runtime]System.Char
IL_003b: unbox.any [System.Runtime]System.Char
IL_0040: stloc.1
// sequence point: hidden
IL_0041: br IL_0179

表面上はよくなったように見えて、パフォーマンス面では悪影響が出ている。
- 😨 結局、二重キャストしている。
- 😨
object
型への変数代入が生じている。e.g.object obj = value
- 😨 boxing/unboxing が生じている。e.g.
IL_000e: box !!T
、IL_0018: unbox.any [System.Runtime]System.Boolean

顧客が本当に必要だったもの
using System;
using System.Runtime.InteropServices;
static class C
{
static ReadOnlySpan<byte> GetBytes( bool value) => BitConverter.GetBytes(value);
static ReadOnlySpan<byte> GetBytes( char value) => BitConverter.GetBytes(value);
static ReadOnlySpan<byte> GetBytes(double value) => BitConverter.GetBytes(value);
static ReadOnlySpan<byte> GetBytes( short value) => BitConverter.GetBytes(value);
static ReadOnlySpan<byte> GetBytes( int value) => BitConverter.GetBytes(value);
static ReadOnlySpan<byte> GetBytes( long value) => BitConverter.GetBytes(value);
static ReadOnlySpan<byte> GetBytes( float value) => BitConverter.GetBytes(value);
static ReadOnlySpan<byte> GetBytes(ushort value) => BitConverter.GetBytes(value);
static ReadOnlySpan<byte> GetBytes( uint value) => BitConverter.GetBytes(value);
static ReadOnlySpan<byte> GetBytes( ulong value) => BitConverter.GetBytes(value);
static ReadOnlySpan<byte> GetBytes<T>(T value) where T : unmanaged => MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref value, 1));
}