RefList`1

というわけでさらさら書いてみます。

.field priate !T&[] store

.property instance !T& Item(int32)
{
  .get instance !T& RefList`1::get_Item(int32)
  .set instnace void RefList`1::set_Item(int32, !T&)
}

.method public hidebysig specialname instance !T& get_Item(int32 index) cil managed
{
  .maxstack 2
  ldarg.0
  ldfld !0[] class RefList`1<!T>::store
  ldarg.1
  ldelem !T&
  stloc.0
  ret
}

.method public hidebysig specialname instance void set_Item(int32 index, !T& value) cil managed
{
  .maxstack 8
  ldarg.0
  ldfld !0[] class RefList`1<!T>::store
  ldarg.1
  ldarg.2
  stelem !T&
  ret
}

これに RefList() : this(10) と RefList(int capacity) のコンストラクタだけ作成してコンパイル。ここまではさくっと通りました。

ここまで終わったところで

FFのメンテ明けのためArgus張りにいってます(笑

Reflector.NET で表示してみる

public class RefList<T> where struct
{
  private ref T[] store;

  public RefList() : this(10) {}

  public RefList(int capacity)
  {
    this.store = new ref T[capacity];
  }

  public ref T this[int index]
  {
    get { return this.store[index];  }
    set { this.store[index] = value; }
  }
}

なかなか期待通りのディスコンパイル結果をだしてくれた。使ってみよう。

public static class Program
{
  static void Main()
  {
    RefList<Rectangle> list = new RefList<Rectangle>;

    Rectangle r = new Rectangle();

    list[0] = new Rectangle();    // 代入できるか?
    list[0].Offset(10, 10);       // メソッド呼べるか?
    Console.WriteLine(list[0]);   // ref Rectangle がどう表示されるか?
  }
}

結果は、下3行が、「CS1501: 引数を1個指定できるメソッド get_Item() のオーバーロードはありません。」となりました。やっぱり無理みたいです。代入文のほうは、

list.set_Item(0, ref r);

とするとコンパイルエラーは消えましたが、取得側は、

list.get_Item(0)

としても CS1501 のままで、ちょっと残念なかんじでした。

もうちょっと遊んでみます。

Argus わかないし(笑

public void Dump()
{
  for (int num1 = 0; num1 < this.store.Length; num1++)
  {
    Console.WriteLine(this.store[num1]);
  }
}

という具合にディスアセンブルされるような IL を記述してみました。テストコードは、

  static void Main()
  {
    RefList<int> list = new RefList<int>(2);

    int n = 1;
    int m = 2;
    
    list.set_Item(0, ref n);
    list.set_Item(0, ref m);
    list.Dump();
    n = 5;
    m = 10;
    list.Dump();
  }

こうです。期待する結果は格納されている値だけ頑張ってくれて

1
2
5
10

なのですが、

ハンドルされていない例外: COMException(略) ByRef 型のフィールドです。(略) HRESULT:0x801312E4
 場所 Program.main()

Dump() の最後は、Console.WriteLine(object) を呼び出していたため、マネージドポインタは渡せないよ!って実行時例外になっちゃたのかな? そもそも where struct してあるので、

  ldelem !T&
  call void System.Console::WriteLine(object)

  ↓

  ldelem !T&
  ldobj !T
  box !T
  call void System.Console::WriteLine(object)

と修正しまみました。Reflector.NET のディスアセンブル結果が、

  for (int num1 = 0; num1 < this.store.Length; num1++)
  {
    Console.WriteLine(*(this.store[num1]));
  }

と、unsafe っぽくなりましたが、これでも同様の例外が出ます。例外文をよくみると、「ByRef 型のフィールド」と書いてあるので、どうも ref T[] store というフィールドが作れないってエラーっぽいですね。*1
ということは、ref T 型ではなくて T 型の配列を保持しておき、get で ref T を返すことで中身を直接変更できるインターフェスにすれば、この問題は解決できるかもしれません。

*1:Main() の .init あたりで落ちてるぽいので、そもそも例外が発生しているのはどうやら Generic クラスの具体型の生成時っぽいです。