C# 9.0ではStructLayout
属性のSize
引数を指定した構造体は通常のコピーでも全部(Size
バイト分)コピーされます。P/Invoke以外ではメンバー変数のカバー範囲だけコピーされるかと思っていましたが、カバー範囲外もコピーされていました。Win32 APIのVARIANT
やPROPVARIANT
のような詳細な定義が面倒な構造体を使う場合は便利な仕様です。
模式図:C# 9.0では構造体の■も□もコピーされる(コピー1でありコピー2ではない)。 ○コピー1:■■■□□→■■■□□ ×コピー2:■■■□□→■■■ →:代入によるコピー操作 ■:構造体のメンバー変数のカバー範囲 □メンバー変数のカバー範囲外
以下のコードで上記を確認できます。StructSize5
は先頭3バイトだけをメンバー変数に持つ絶対サイズ5バイト(StructLayout.Size == 5
)の構造体です。コピー前の構造体で5バイト目に書き込んだ値がコピー後の構造体でも保持されていたことから、構造体の絶対サイズ分(全体)がコピーされることが分かります。
using System; using System.Runtime.InteropServices; var struct1 = new StructSize5(new byte[] { 0, 1, 2, 3, 4 }); // 値セマンティクスによりインスタンスがコピーされる。 var struct2 = struct1; var copied = struct2.ReadBytes()[4] == 4; Console.WriteLine( "メンバー変数の範囲外はコピーされま{0}", copied ? "した。" : "せんでした。"); [StructLayout(LayoutKind.Sequential, Size = 5)] readonly struct StructSize5 { // 確認用の先頭3バイト public readonly byte Byte0; public readonly byte Byte1; public readonly byte Byte2; public StructSize5(byte[] bytes) { (Byte0, Byte1, Byte2) = (0, 0, 0); if (bytes.Length > Marshal.SizeOf(this)) throw new ArgumentOutOfRangeException(nameof(bytes)); NativeMethods.RtlCopyMemory(ref this, bytes, (uint)bytes.Length); } public byte[] ReadBytes() { var bytes = new byte[Marshal.SizeOf(this)]; NativeMethods.RtlCopyMemory(bytes, this, (uint)bytes.Length); return bytes; } private static class NativeMethods { [DllImport("ntdll.dll")] public static extern void RtlCopyMemory([Out] byte[] Destination, in StructSize5 Source, uint Length); [DllImport("ntdll.dll")] public static extern void RtlCopyMemory(ref StructSize5 Destination, [In] byte[] Source, uint Length); } }
Main関数(C# 8)
using System; using System.Runtime.InteropServices; static class Program { static void Main() { var struct1 = new StructSize5(new byte[] { 0, 1, 2, 3, 4 }); // 値セマンティクスによりインスタンスがコピーされる。 var struct2 = struct1; var copied = struct2.ReadBytes()[4] == 4; Console.WriteLine( "メンバー変数の範囲外はコピーされま{0}", copied ? "した。" : "せんでした。"); } [StructLayout(LayoutKind.Sequential, Size = 5)] struct StructSize5 { // 確認用の先頭3バイト public readonly byte Byte0; public readonly byte Byte1; public readonly byte Byte2; public StructSize5(byte[] bytes) { (Byte0, Byte1, Byte2) = (0, 0, 0); if (bytes.Length > Marshal.SizeOf(this)) throw new ArgumentOutOfRangeException(nameof(bytes)); NativeMethods.RtlCopyMemory(ref this, bytes, (uint)bytes.Length); } public byte[] ReadBytes() { var bytes = new byte[Marshal.SizeOf(this)]; NativeMethods.RtlCopyMemory(bytes, this, (uint)bytes.Length); return bytes; } private static class NativeMethods { [DllImport("ntdll.dll")] public static extern void RtlCopyMemory([Out] byte[] Destination, in StructSize5 Source, uint Length); [DllImport("ntdll.dll")] public static extern void RtlCopyMemory(ref StructSize5 Destination, [In] byte[] Source, uint Length); } } }
参考