FieldInfo.SetValueメソッドを使って構造体のフィールドに値を設定する方法について。

メソッドに構造体(値型)のインスタンスを渡す場合、メソッドにはインスタンスのコピーが渡される(方法 : メソッドに構造体を渡すこととクラス参照を渡すことの違いを理解する (C# プログラミング ガイド))。

FieldInfo.SetValueも同様で、構造体のインスタンスを渡してもフィールドの値が変更されるのはコピーされたインスタンスの方で、元のインスタンスの方ではない。 そのため、FieldInfo.SetValueに構造体インスタンスを指定しても、見た目上はフィールドの値が変更されないよう見える。

using System;
using System.Reflection;

struct S {
  public int F;
}

class Test {
  static void Main()
  {
    var s = new S();
    var f = s.GetType().GetField("F");

    // 構造体SのフィールドFに値42をセット
    f.SetValue(s, 42);

    // フィールドにセットされた値を表示
    Console.WriteLine(s.F);
  }
}
実行結果
0

FieldInfo.SetValueメソッドには参照渡し(ref/ByRef)を行うようなオーバーロードは用意されていないので、FieldInfo.SetValueメソッドで構造体フィールドの値を設定するにはなんらかの代替策を取る必要がある。

§1 ボックス化する

Object型に構造体インスタンスを代入することによりボックス化を行い、ボックス化したものをFieldInfo.SetValueメソッドに指定することで参照渡しする。 その後、ボックス化を解除して構造体型に戻す。

using System;
using System.Reflection;

struct S {
  public int F;
}

class Test {
  static void Main()
  {
    var s = new S();
    var f = s.GetType().GetField("F");

    // 一旦構造体インスタンスをボックス化する
    object boxed = s;

    // ボックス化したインスタンスを渡してフィールドに値をセット
    f.SetValue(boxed, 42);

    // ボックス化を解除する
    s = (S)boxed;

    // フィールドにセットされた値を表示
    Console.WriteLine(s.F);
  }
}

VB.NETでは、このような方法でボックス化することはできない。

§2 ValueTypeを使う

Object型に代入してボックス化するかわりに、一旦ValueTypeクラスに変換してFieldInfo.SetValueメソッドに渡す。

using System;
using System.Reflection;

struct S {
  public int F;
}

class Test {
  static void Main()
  {
    var s = new S();
    var f = s.GetType().GetField("F");

    // 一旦インスタンスをValueTypeに変換する
    ValueType v = s;

    // 変換したインスタンスを渡してフィールドに値をセット
    f.SetValue(v, 42);

    // 元の構造体型に戻す
    s = (S)v;

    // フィールドにセットされた値を表示
    Console.WriteLine(s.F);
  }
}
実行結果
42

§3 FieldInfo.SetValueDirectメソッドを使う

FieldInfo.SetValueメソッドの代わりに、FieldInfo.SetValueDirectメソッドを使う。 FieldInfo.SetValueDirectメソッドでは、値を設定したいインスタンスを指定するかわりに、TypedReferenceを指定する。 C#では、隠しキーワード __makeref を使用することでTypedReferenceを取得することができる。

using System;
using System.Reflection;

struct S {
  public int F;
}

class Test {
  static void Main()
  {
    var s = new S();
    var f = s.GetType().GetField("F");

    // __makerefキーワードを使って参照を取得し、SetValueDirectでフィールドに値をセット
    f.SetValueDirect(__makeref(s), 42);

    // フィールドにセットされた値を表示
    Console.WriteLine(s.F);
  }
}
実行結果
42