[C#] Unsafeクラスの各メソッドを簡易解説(逆引き)

`System.Runtime.CompilerServices.Unsafe` クラスは一部で大変便利なのですが、ドキュメントを見ても動作が分かりにくいことが多いので、実際のILとC#へ逆コンパイルした結果を併記して簡易解説(メモ)します。

- 本質的でない属性は除去しています。  
  例: `.custom instance void System.Runtime.Versioning.NonVersionableAttribute::.ctor() = ( 01 00 00 00 )`
- ニュアンスをつかみやすくするため、ILSpy version 6.0.0.5830-rc1 での逆コンパイル結果を併記しています。
- 逆コンパイル結果がおかしい場合がありますが、ILをC#的に表現できないものがあるので気にしません。
- [ILコードをリポジトリから丸コピー](https://github.com/dotnet/corefx/blob/eaa464046559e4e1d616e1e3274b60d91432ad3a/src/System.Runtime.CompilerServices.Unsafe/src/System.Runtime.CompilerServices.Unsafe.il)しているので、ライセンスはThe MIT License (MIT)になります。

## ILについて

- `.`で始まるものはディレクティブ
- `&`はそのまま「参照」。C++/CLIでいうインテリア参照
- `!!T`は、`T`という名前のメソッドのジェネリック型引数です。
- `native int` は`IntPtr`です。
- スタックベースの仮想マシンです。引数はスタックに積まれています。この記事で頻出の命令をメモしておきます。
  - 関数の引数は右側を先にスタックに積みます。
  - `ldarg.N`で引数のN番目をスタックにロードします。
  - `.maxstack N` で使用する最大のスタック数を宣言します。
  - `cpblk` スタックから、コピー先、コピー元、バイト数をポップして、コピーする。コピー元とコピー先の領域とが重なっている場合，cpblkの動作は未規定。
- 詳しくは[Wikipediaのリスト](https://en.wikipedia.org/wiki/List_of_CIL_instructions)か [PDF](https://download.microsoft.com/download/7/3/3/733ad403-90b2-4064-a81e-01035a7fe13c/ms%20partition%20iii.pdf)を見てください。

| 用語               | この記事での使い方                                                       |
| :----------------- | :----------------------------------------------------------------------- |
| `ref`              | IL的にはマネージドポインターですが、長いのでC#の`ref`として表記します。  |
| ポインター         | 「マネージドポインター」以外は、アンマネージドポインターとして用います。 |
| オブジェクト参照   | 参照型のインスタンス                                                     |
| アライメントされた | アーキテクチャに依存するアドレスに配置された                             |

## 参照・ポインターの再解釈系

基本的に`ldarg.0`して`ret`する、つまり引数をそのまま戻り値の型として呼び出し元に返すだけ。

### ジェネリック型の`ref`をvoidポインターとして解釈する

`conv.u` で参照をポインターに変換しているっぽい。

```csharp
public unsafe static void* AsPointer<T>(ref T value)
{
	return Unsafe.AsPointer(ref value);
}
```

```il
.method public hidebysig static void* AsPointer<T>(!!T& 'value') cil managed aggressiveinlining
{
    .maxstack 1
    ldarg.0
    conv.u
    ret
} // end of method Unsafe::AsPointer
```

### 型チェックをスキップして、`object`型を参照型のジェネリック型に再解釈する

純粋に再解釈するので誤ったキャストも許容される。
キャストの場合は、`unbox.any !!T`命令が使用される。

```csharp
public static T As<T>(object o) where T : class
{
	return (T)o;
}
```

```il
.method public hidebysig static !!T As<class T>(object o) cil managed aggressiveinlining
{
    .maxstack 1
    ldarg.0
    ret
} // end of method Unsafe::As
```

### あるジェネリック型の`ref`を、別のジェネリック型の`ref`に再解釈する

C++でいうと`TFrom& source = ...; return (TTo&)source;` といったイメージ。

```csharp
public static ref TTo As<TFrom, TTo>(ref TFrom source)
{
	return ref Unsafe.As<TFrom, TTo>(ref source);
}
```

```il
.method public hidebysig static !!TTo& As<TFrom, TTo>(!!TFrom& source) cil managed aggressiveinlining
{
    .maxstack 1
    ldarg.0
    ret
} // end of method Unsafe::As
```

### voidポインターをジェネリック型の`ref`に再解釈する


```csharp
public unsafe static ref T AsRef<T>(void* source)
{
	return ref *(T*)source;
}
```

```il
.method public hidebysig static !!T& AsRef<T>(void* source) cil managed aggressiveinlining
{
// For .NET Core the roundtrip via a local is no longer needed see:
// https://github.com/dotnet/coreclr/issues/13341
// and
// https://github.com/dotnet/coreclr/pull/11218
#ifdef netcoreapp
    .maxstack 1
    ldarg.0
    ret
#else
    .locals (int32&)
    .maxstack 1
    ldarg.0
    // Roundtrip via a local to avoid type mismatch on return that the JIT inliner chokes on.
    stloc.0
    ldloc.0
    ret
#endif
} // end of method Unsafe::AsRef
```

### `readonly ref`をジェネリック型の`ref`に再解釈する

```csharp
public static ref T AsRef<T>(in T source)
{
	return ref source;
}
```

`.param [1]` で仮引数1(1オリジン)に属性を適用している。

```il
.method public hidebysig static !!T& AsRef<T>(!!T& source) cil managed aggressiveinlining
{
    .param [1]
#ifdef netcoreapp
    .custom instance void [CORE_ASSEMBLY]System.Runtime.CompilerServices.IsReadOnlyAttribute::.ctor() = ( 01 00 00 00 )
#else
    .custom instance void System.Runtime.CompilerServices.IsReadOnlyAttribute::.ctor() = ( 01 00 00 00 )
#endif
    .maxstack 1
    ldarg.0
    ret
} // end of method Unsafe::AsRef
```

## コピー/読み取り/書き込み系

### アラインメントされたvoidポインターから、読んだ値を返す

Cでいうと`*((T*)source);`

```csharp
public unsafe static T Read<T>(void* source)
{
	return *(T*)source;
}
```

```il
.method public hidebysig static !!T Read<T>(void* source) cil managed aggressiveinlining
{
    .maxstack 1
    ldarg.0
    ldobj !!T
    ret
} // end of method Unsafe::Read
```

### アラインメントされていないポインタから、読んだ値を返す

IA(x86, x64アーキテクチャ)では、恐らくUnalignedが付かない方を使用しても、(性能以外で)ペナルティはない。
逆コンパイル結果はアライメントされたものと同じだが、C#で表現できないからだろう。

```csharp
public unsafe static T ReadUnaligned<T>(void* source)
{
	return *(T*)source;
}
```

```il
.method public hidebysig static !!T ReadUnaligned<T>(void* source) cil managed aggressiveinlining
{
    .maxstack 1
    ldarg.0        
    unaligned. 0x1
    ldobj !!T
    ret
} // end of method Unsafe::ReadUnaligned
```

### アラインメントされていないbyte型のマネージドポインターから、読んだ値を返す

逆コンパイル結果はアライメントされたものと同じだが、C#で表現できないからだろう。

```csharp
public unsafe static T ReadUnaligned<T>(ref byte source)
{
	return *(T*)(&source);
}
```

```il
.method public hidebysig static !!T ReadUnaligned<T>(uint8& source) cil managed aggressiveinlining
{
    .maxstack 1
    ldarg.0        
    unaligned. 0x1
    ldobj !!T
    ret
} // end of method Unsafe::ReadUnaligned
```

### アラインメントされたvoidポインターへ値を書き込む

Cでいうと`*((T*)) = value;`

```csharp
public unsafe static void Write<T>(void* destination, T value)
{
	*(T*)destination = value;
}
```

```il
.method public hidebysig static void Write<T>(void* destination,
                                                !!T 'value') cil managed aggressiveinlining
{
    .maxstack 2
    ldarg.0
    ldarg.1
    stobj !!T
    ret
} // end of method Unsafe::Write
```

### アラインメントされていないvoidポインターへ値を書き込む

Cでいうと`*((__unaligned T*)) = value;`

```csharp
public unsafe static void WriteUnaligned<T>(void* destination, T value)
{
	*(T*)destination = value;
}
```

```il
.method public hidebysig static void WriteUnaligned<T>(void* destination,
                                                !!T 'value') cil managed aggressiveinlining
{
    .maxstack 2
    ldarg.0
    ldarg.1
    unaligned. 0x01
    stobj !!T
    ret
} // end of method Unsafe::WriteUnaligned
```

### アラインメントされていないbyte型の`ref`へ値を書き込む

```csharp
public unsafe static void WriteUnaligned<T>(ref byte destination, T value)
{
	*(T*)(&destination) = value;
}
```

```il
.method public hidebysig static void WriteUnaligned<T>(uint8& destination,
                                                !!T 'value') cil managed aggressiveinlining
{
    .maxstack 2
    ldarg.0
    ldarg.1        
    unaligned. 0x01
    stobj !!T
    ret
} // end of method Unsafe::WriteUnaligned
```

### ジェネリック型の`ref`からvoidポインターへコピーする

```csharp
public unsafe static void Copy<T>(void* destination, ref T source)
{
	*(T*)destination = source;
}
```

```il
.method public hidebysig static void Copy<T>(void* destination,
                                            !!T& source) cil managed aggressiveinlining
{
    .maxstack 2
    ldarg.0
    ldarg.1
    ldobj !!T
    stobj !!T
    ret
} // end of method Unsafe::Copy
```

### voidポインターからジェネリック型の`ref`へコピーする

```csharp
public unsafe static void Copy<T>(ref T destination, void* source)
{
	destination = *(T*)source;
}
```

```il
.method public hidebysig static void Copy<T>(!!T& destination,
                                            void* source) cil managed aggressiveinlining
{
    .maxstack 2
    ldarg.0
    ldarg.1
    ldobj !!T
    stobj !!T
    ret
} // end of method Unsafe::Copy
```

### voidポインターから指定されたバイト数分、voidポインターへコピーする(Cで云う`memcpy`)

```csharp
public unsafe static void CopyBlock(void* destination, void* source, uint byteCount)
{
	// IL cpblk instruction
	Unsafe.CopyBlock(destination, source, byteCount);
}
```

```il
.method public hidebysig static void CopyBlock(void* destination, void* source, uint32 byteCount) cil managed aggressiveinlining
{
    .maxstack 3
    ldarg.0
    ldarg.1
    ldarg.2
    cpblk
    ret
} // end of method Unsafe::CopyBlock
```

### byte型の`ref`から指定されたバイト数分、byte型の`ref`へコピーする(Cで云う`memcpy`)

```csharp
public static void CopyBlock(ref byte destination, ref byte source, uint byteCount)
{
	// IL cpblk instruction
	Unsafe.CopyBlock(ref destination, ref source, byteCount);
}
```

```il
.method public hidebysig static void CopyBlock(uint8& destination, uint8& source, uint32 byteCount) cil managed aggressiveinlining
{
    .maxstack 3
    ldarg.0
    ldarg.1
    ldarg.2
    cpblk
    ret
} // end of method Unsafe::CopyBlock
```

### アラインメントされていないvoidポインターから指定されたバイト数分、voidポインターへコピーする

```csharp
public unsafe static void CopyBlockUnaligned(void* destination, void* source, uint byteCount)
{
	// IL cpblk instruction
	Unsafe.CopyBlockUnaligned(destination, source, byteCount);
}
```


```il
.method public hidebysig static void CopyBlockUnaligned(void* destination, void* source, uint32 byteCount) cil managed aggressiveinlining
{
    .maxstack 3
    ldarg.0
    ldarg.1
    ldarg.2
    unaligned. 0x1
    cpblk
    ret
} // end of method Unsafe::CopyBlockUnaligned
```

### アラインメントされていないbyte型の`ref`から指定されたバイト数分、voidポインターへコピーする

```csharp
public static void CopyBlockUnaligned(ref byte destination, ref byte source, uint byteCount)
{
	// IL cpblk instruction
	Unsafe.CopyBlockUnaligned(ref destination, ref source, byteCount);
}
```

```il
.method public hidebysig static void CopyBlockUnaligned(uint8& destination, uint8& source, uint32 byteCount) cil managed aggressiveinlining
{
    .maxstack 3
    ldarg.0
    ldarg.1
    ldarg.2
    unaligned. 0x1
    cpblk
    ret
} // end of method Unsafe::CopyBlockUnaligned
```

### voidポインターへ指定されたバイト数分、バイト値を書き込む(Cで云う`memset`)

```csharp
public unsafe static void InitBlock(void* startAddress, byte value, uint byteCount)
{
	// IL initblk instruction
	Unsafe.InitBlock(startAddress, value, byteCount);
}
```

```il
.method public hidebysig static void InitBlock(void* startAddress, uint8 'value', uint32 byteCount) cil managed aggressiveinlining
{
    .maxstack 3
    ldarg.0
    ldarg.1
    ldarg.2
    initblk
    ret
} // end of method Unsafe::InitBlock
```

### byte型の`ref`へ指定されたバイト数分、バイト値を書き込む(Cで云う`memset`)

```csharp
public static void InitBlock(ref byte startAddress, byte value, uint byteCount)
{
	// IL initblk instruction
	Unsafe.InitBlock(ref startAddress, value, byteCount);
}
```

```il
.method public hidebysig static void InitBlock(uint8& startAddress, uint8 'value', uint32 byteCount) cil managed aggressiveinlining
{
    .maxstack 3
    ldarg.0
    ldarg.1
    ldarg.2
    initblk
    ret
} // end of method Unsafe::InitBlock
```

### アライメントされていないvoidポインターへ指定されたバイト数分、バイト値を書き込む(Cで云う`memset`)

```csharp
public unsafe static void InitBlockUnaligned(void* startAddress, byte value, uint byteCount)
{
	// IL initblk instruction
	Unsafe.InitBlockUnaligned(startAddress, value, byteCount);
}
```

```il
.method public hidebysig static void InitBlockUnaligned(void* startAddress, uint8 'value', uint32 byteCount) cil managed aggressiveinlining
{
    .maxstack 3
    ldarg.0
    ldarg.1
    ldarg.2
    unaligned. 0x1
    initblk
    ret
} // end of method Unsafe::InitBlockUnaligned
```

### アライメントされていないbyte型の`ref`へ指定されたバイト数分、バイト値を書き込む(Cで云う`memset`)

```csharp
public static void InitBlockUnaligned(ref byte startAddress, byte value, uint byteCount)
{
	// IL initblk instruction
	Unsafe.InitBlockUnaligned(ref startAddress, value, byteCount);
}
```

```il
.method public hidebysig static void InitBlockUnaligned(uint8& startAddress, uint8 'value', uint32 byteCount) cil managed aggressiveinlining
{
    .maxstack 3
    ldarg.0
    ldarg.1
    ldarg.2
    unaligned. 0x1
    initblk
    ret
} // end of method Unsafe::InitBlockUnaligned
```

## アドレス演算系

### ジェネリック型の`ref`を要素数分の加算する

C++で云う`(T&)(((T*)&source)+elementOffset)`

```csharp
public static ref T Add<T>(ref T source, int elementOffset)
{
	return ref Unsafe.Add(ref source, elementOffset);
}
```

```il
.method public hidebysig static !!T& Add<T>(!!T& source, int32 elementOffset) cil managed aggressiveinlining
{
    .maxstack 3
    ldarg.0
    ldarg.1
    sizeof !!T
    conv.i
    mul
    add
    ret
} // end of method Unsafe::Add
```

### ジェネリック型の`ref`を要素数分加算する

こちらはnative int版

```csharp
public static ref T Add<T>(ref T source, IntPtr elementOffset)
{
	return ref Unsafe.Add(ref source, elementOffset);
}
```

```il
.method public hidebysig static !!T& Add<T>(!!T& source, native int elementOffset) cil managed aggressiveinlining
{
    .maxstack 3
    ldarg.0
    ldarg.1
    sizeof !!T
    mul
    add
    ret
} // end of method Unsafe::Add
```

### voidポインターをジェネリック型として要素数分加算する

```csharp
public unsafe static void* Add<T>(void* source, int elementOffset)
{
	return (byte*)source + (long)elementOffset * (long)sizeof(T);
}
```

```il
.method public hidebysig static void* Add<T>(void* source, int32 elementOffset) cil managed aggressiveinlining
{
    .maxstack 3
    ldarg.0
    ldarg.1
    sizeof !!T
    conv.i
    mul
    add
    ret
} // end of method Unsafe::Add
```

### ジェネリック型の`ref`へ指定バイト数分加算する

```csharp
public static ref T AddByteOffset<T>(ref T source, IntPtr byteOffset)
{
	return ref Unsafe.AddByteOffset(ref source, byteOffset);
}
```

```il
.method public hidebysig static !!T& AddByteOffset<T>(!!T& source, native int byteOffset) cil managed aggressiveinlining
{
    .maxstack 2
    ldarg.0
    ldarg.1
    add
    ret
} // end of method Unsafe::AddByteOffset
```

### ジェネリック型の`ref`を要素数分減算する

`ref *(((T*)&source) - elementOffset)`

```csharp
public static ref T Subtract<T>(ref T source, int elementOffset)
{
	return ref Unsafe.Subtract(ref source, elementOffset);
}
```

```il
.method public hidebysig static !!T& Subtract<T>(!!T& source, int32 elementOffset) cil managed aggressiveinlining
{
    .maxstack 3
    ldarg.0
    ldarg.1
    sizeof !!T
    conv.i
    mul
    sub
    ret
} // end of method Unsafe::Subtract
```

### ジェネリック型の`ref`を要素数分加算する

こちらはnative int版

```csharp
public static ref T Subtract<T>(ref T source, IntPtr elementOffset)
{
	return ref Unsafe.Subtract(ref source, elementOffset);
}
```

```il
.method public hidebysig static !!T& Subtract<T>(!!T& source, native int elementOffset) cil managed aggressiveinlining
{
    .maxstack 3
    ldarg.0
    ldarg.1
    sizeof !!T
    mul
    sub
    ret
} // end of method Unsafe::Subtract
```

### voidポインターをジェネリック型として要素数分減算する

`ref *(((T*)source) - elementOffset)`

```csharp
public unsafe static void* Subtract<T>(void* source, int elementOffset)
{
	return (byte*)source - (long)elementOffset * (long)sizeof(T);
}
```

```il
.method public hidebysig static void* Subtract<T>(void* source, int32 elementOffset) cil managed aggressiveinlining
{
    .maxstack 3
    ldarg.0
    ldarg.1
    sizeof !!T
    conv.i
    mul
    sub
    ret
} // end of method Unsafe::Subtract
```

### ジェネリック型の`ref`へ指定バイト数分減算する

`ref *((T*)(((byte*)&source) - byteOffset))`

```csharp
public static ref T SubtractByteOffset<T>(ref T source, IntPtr byteOffset)
{
	return ref Unsafe.SubtractByteOffset(ref source, byteOffset);
}
```

```il
.method public hidebysig static !!T& SubtractByteOffset<T>(!!T& source, native int byteOffset) cil managed aggressiveinlining
{
    .maxstack 2
    ldarg.0
    ldarg.1
    sub
    ret
} // end of method Unsafe::SubtractByteOffset
```

### `ref`と同じ型の別の`ref`の差を求める

`(byte*)(ref right) - (byte*)(ref left)`

```csharp
public static IntPtr ByteOffset<T>(ref T origin, ref T target)
{
	return Unsafe.ByteOffset(ref target, ref origin);
}
```

```il
.method public hidebysig static native int ByteOffset<T>(!!T& origin, !!T& target) cil managed aggressiveinlining
{
    .maxstack 2
    ldarg.1
    ldarg.0
    sub
    ret
} // end of method Unsafe::ByteOffset
```

## アドレス比較系

### `ref`が同じ値かどうかを比較する
```csharp
public static bool AreSame<T>(ref T left, ref T right)
{
	return (ref left) == (ref right);
}
```

```il
.method public hidebysig static bool AreSame<T>(!!T& left, !!T& right) cil managed aggressiveinlining
{
    .maxstack 2
    ldarg.0
    ldarg.1
    ceq
    ret 
} // end of method Unsafe::AreSame
```

### `ref`が右辺より大きいか比較する
```csharp
public static bool IsAddressGreaterThan<T>(ref T left, ref T right)
{
	return (ref left) > (ref right);
}
```

```il
.method public hidebysig static bool IsAddressGreaterThan<T>(!!T& left, !!T& right) cil managed aggressiveinlining
{
    .maxstack 2
    ldarg.0
    ldarg.1
    cgt.un
    ret 
} // end of method Unsafe::IsAddressGreaterThan
```

### `ref`が右辺より小さいか比較する
```csharp
public static bool IsAddressLessThan<T>(ref T left, ref T right)
{
	return (ref left) < (ref right);
}
```

```il
.method public hidebysig static bool IsAddressLessThan<T>(!!T& left, !!T& right) cil managed aggressiveinlining
{
    .maxstack 2
    ldarg.0
    ldarg.1
    clt.un
    ret 
} // end of method Unsafe::IsAddressLessThan
```


## 他

### マーシャリングを考慮しないサイズを求める

unsafeコンテキストで`sizeof()`することと同じ。
`Marshal.SizeOf()`はマーシャリングを考慮する。

個人的には`where T : unmanaged`をつけて欲しい。

```csharp
public unsafe static int SizeOf<T>()
{
	return sizeof(T);
}
```

```il
.method public hidebysig static int32 SizeOf<T>() cil managed aggressiveinlining
{
    .maxstack 1
    sizeof !!T
    ret
} // end of method Unsafe::SizeOf
```

## .NET Core 3以降またはNuGet パッケージの最新(少なくとも4.7.1以降)

### box化された構造体(オブジェクト参照)の内部の`ref`を取得する

C++/CLIで云う`cli::interior_ptr`と等価。

```csharp
public static ref T Unbox<T>(object box) where T : struct
{
	return ref (T)box;
}
```


```il
.method public hidebysig static !!T& Unbox<valuetype .ctor ([CORE_ASSEMBLY]System.ValueType) T> (object 'box') cil managed aggressiveinlining
{
    .maxstack 1
    ldarg.0
    unbox !!T
    ret
} // end of method Unsafe::Unbox
```

## .NET 5以降

### 初期化(0クリア)をスキップして変数宣言する

~~多分CLRの対応が必要~~ https://github.com/dotnet/runtime/issues/29905

```il
  .method public hidebysig static void SkipInit<T> ([out] !!T& 'value') cil managed aggressiveinlining
  {
        .maxstack 0
        ret
  } // end of method Unsafe::SkipInit

```

## 個人的に欲しいメソッド

組み合わせれば実現するが、できれば直接欲しいと思っているメソッドの一覧。

- Cで云う`offsetof`
- Cで云う`memmove`  
  `cpblk`に領域が重なっている場合の保証がないので。
- `readonly ref`への強制再解釈
- 全体的に`readonly ref`版のメソッド
必要無ければ、`readonly`にしてほしいところだが、オーバーロードできないので多分厳しい。
- アライメント判定
- Overlaps判定(`Span<T>`にはあるけど。)
- ジェネリック型`T where T : unmanaged`のマネージドポインターをポインターへの再解釈
```
public unsafe static T* ATypedsPointer<T>(ref T value) where T : unmanaged
{
	return (T*)Unsafe.AsPointer(ref value);
}
```