自分的ILノート

# はじめに

これは私がILについて調べたことを忘れないようにメモする場所です。前提知識が確実に欠如しております。
間違いがございましたらご指摘お願いします。
特に明示しない場合は .NET Framework 4.8 を使い、規格はECMA-335に準ずるものとします。
どんどん更新していきます。（気分次第）

# ジェネリクス

~~~cs
class C<T>
{
    T inst;
}
~~~
の`T`は`!0`になる。よって上記のコードは

~~~il
.class private auto ansi beforefieldinit C`1<T> extends [mscorlib]System.Object
{
    .field private !0 inst
    // .ctor 省略
}
~~~
簡単に書くと

~~~il
.class private C`1<T>
{
    .field private !0 inst
}
~~~
※```C`1<T>```は`C<T>`でもOK。ただし、`class C<T>`と`class C`が両方あると.NET言語では片方しか参照できない。最初に書いた方が参照できる。

## 説明
[ECMA-335 II.7.1 Types](https://www.ecma-international.org/publications/files/ECMA-ST/ECMA-335.pdf#%5B%7B%22num%22%3A1725%2C%22gen%22%3A0%7D%2C%7B%22name%22%3A%22XYZ%22%7D%2C87%2C632%2C0%5D) では、型定義のジェネリックパラメータは`'!' Int32`、メソッド定義のジェネリックパラメータは`'!!' Int32`となっている（Int32は0から数えたindex）。つまり、`Class<T,U>.Method<V>()`があったとすると、`T`は`!0`、`U`は`!1`、`V`は`!!0`となる。

### ちょいまてや
[SharpLabで見てみると](https://sharplab.io/#v2:C4LglgNgPgAgTARgLAChUwQNgAT2wYQgEMBnEgHgBUAaAVQD4BvVbV3LXAFmwFkBTYAAsA9gBNyANXoAKAJTMUbJewCc04AE8ADn2EAzaZVmyA3C2WsMazTv3Taxs4our123QYmPzrAL6pfIA===) `ldtoken !T`とか`ldtoken !!V`とかになってるじゃないですか！　`ldtoken !0`、`ldtoken !!0`じゃないの？

### ilasm.exe をつかうか
ilasm.exe で以下のようなコードをコンパイルしてみた。

~~~il
.assembly extern mscorlib {}
.assembly generics {}

.class public Program
{
    .method static void Main() 
    {
        .entrypoint
        call class [mscorlib]System.Collections.Generic.List`1<!0> class C<int32>::GetList()
        call instance class [mscorlib]System.Type [mscorlib]System.Object::GetType()
        call void [mscorlib]System.Console::WriteLine(object)
        ret
    }
}

.class public abstract sealed C`1<T>
{
    .method public static class [mscorlib]System.Collections.Generic.List`1<!T> GetList()
    {
        newobj instance void class [mscorlib]System.Collections.Generic.List`1<!T>::.ctor()
        ret       
    }
}
~~~

#### 実行結果
~~~
System.Collections.Generic.List`1[System.Int32]
~~~
え、普通にいけるやん……。
ECMA-335ではこんな記法書いてなかったんだけどな……。

### 考察
* `T`→`!T`なら、コンパイラ負荷は少なそう。
    * ソースコード→ILのコード→ILのバイナリーコード であればわざわざindex調べる必要がある`!0`や`!!0`のような書き方より`!T`、`!!T`にした方がコンパイル速度も速そう。
* IL（というかアセンブリ）は多様な書き方を許すべきであるはず。
    * わざわざ`T`と名前がついているのにオペランドに`T`を用いてはいけません、とする理由はない。
* `!T`は型パラメータを指して、`!0`は入力された型を指すのでは？
    * ```call class [mscorlib]System.Collections.Generic.List`1<!T> class C<int32>::GetList()```ができないのかその証拠。
    * `!T`の`List`を返すのではなく、`!0`、つまり`int32`の`List`を返すのだから。
    * `call`命令の時点で型は決まっているから、抽象的な型パラメータを用いるのは間違っている。
    * ちなみに ```call class [mscorlib]System.Collections.Generic.List`1<int32> class C<int32>::GetList()``` は ```.method public static class [mscorlib]System.Collections.Generic.List`1<int32> GetList()``` を指すため不可。

## ジェネリクスの共変性・反変性
ジェネリクスの共変性・反変性については [MSDN](https://docs.microsoft.com/ja-jp/dotnet/standard/generics/covariance-and-contravariance) を参照。
※ MSDN の タイトルは「ジェネリックの共変性と反変性」になってるけど、元（英文）だと "Covariance and Contravariance in Generics" だから、ジェネリクスじゃないかな？

### IL での指定方法

ジェネリクスの共変性・反変性は、C# では `<in T>` や `<out T>` に当たる。
[ECMA-335 II.9.5 Generics variance](https://www.ecma-international.org/publications/files/ECMA-ST/ECMA-335.pdf#%5B%7B%22num%22%3A1816%2C%22gen%22%3A0%7D%2C%7B%22name%22%3A%22XYZ%22%7D%2C87%2C659%2C0%5D) によると、`<out T>` は `<+T>`、`<in T>` は `<-T>` と書くらしい。

では、```I`1<+T>``` は、 ```I`1<string>``` → ```I`1<object>``` の代入はできて、 ```I`1<object>``` → ```I`1<string>``` の代入はできないはず。 

~~~il:sample.il
.assembly extern mscorlib {}
.assembly generics {}

.class public Program
{
    .method static void Main() 
    {
        .locals init (
            [0] class I`1<string>,
            [1] class I`1<object>
        )
        .entrypoint
        newobj instance void class C`1<string>::.ctor()
        stloc.0 // もちろんOK
        
        ldstr "string => string Succeeded!"
        call void [mscorlib]System.Console::WriteLine(string)
        
        newobj instance void class C`1<string>::.ctor()
        stloc.1 // OK

        ldstr "string => object Succeeded!"
        call void [mscorlib]System.Console::WriteLine(string)

        newobj instance void class C`1<object>::.ctor()
        stloc.0 // NG

        ldstr "object => string Succeeded!"
        call void [mscorlib]System.Console::WriteLine(string)

        ret
    }
}

.class public C`1<T> extends [mscorlib]System.Object implements class I`1<!T>
{
    .method public specialname rtspecialname instance void .ctor() cil managed
    {
        ldarg.0
        call instance void [mscorlib]System.Object::.ctor()
        ret
    }
}

.class interface public abstract I`1<+T> { }
~~~

#### 実行結果
~~~
string => string Succeeded!
string => object Succeeded!
object => string Succeeded!
~~~

あら？ できた……。なんで例外吐かないの？

### 一応 C# で確認

もちろん、C# ではコンパイルエラーになる。
確かめるために近似したコードを書いてコンパイルしてみる。

~~~cs:近似コード.cs
using System;

public class Program
{
    static void Main()
    {
        I<string> istr = new C<string>(); // もちろんOK
        Console.WriteLine("string => string Succeeded!");
        I<object> iobj = new C<string>(); // OK
        Console.WriteLine("string => object Succeeded!");
        istr = new C<object>(); // NG コンパイルエラー
        Console.WriteLine("object => string Succeeded!");
    }
}

public class C<T> : I<T> { }

public interface I<out T> { }
~~~
#### 実行結果
コンパイルエラー。