- PR -

FieldInfoのSetValueメソッド

1
投稿者投稿内容
やぎ
会議室デビュー日: 2003/04/01
投稿数: 14
投稿日時: 2003-04-08 18:20
こんにちは。
訳あって、FieldInfoクラスを使用して値の入出力を行いたいのですが、
SetValueメソッドが期待する動きをしてくれません。
以下がそのソースですが、最終行で"ABC"とメッセージボックスに表示
されません。
書き方間違ってますか?
ちなみにp.Name="ABC"として値を設定後、GetValueメソッドを呼び出すと
正常に"ABC"が表示されます。
よろしくお願いします。

コード:

Public Structure Person
Public Age As Integer
Public Name As String
End Structure

Dim p As New Person()
Dim myType As Type = GetType(Person)
Dim fi As FieldInfo = myType.GetField("Name")

fi.SetValue(p, "ABC")
MsgBox(fi.GetValue(p))




[ メッセージ編集済み 編集者: やぎ 編集日時 2003-04-08 18:24 ]
Qoo
大ベテラン
会議室デビュー日: 2003/04/08
投稿数: 121
投稿日時: 2003-04-08 19:01
こんにちは。
Personを構造体でなく、クラスにするわけにはいきません?
やぎ
会議室デビュー日: 2003/04/01
投稿数: 14
投稿日時: 2003-04-08 19:13
Qooさん、ありがとうございます。

VB6からの移行で、構造体が半端な量ではないため、
できればクラスにはしたくないのですが・・・
やっぱりクラスでないと無理なのでしょうか?
NothingButXMLInfoSet
ベテラン
会議室デビュー日: 2003/03/31
投稿数: 65
投稿日時: 2003-04-08 22:09
すみません、直接の返答ではありません。どうやらVB.NETではやぎさんの求める処理はできないようです。

実は、C#ではできるんです。問題は、Qooさんがおっしゃっているとおり、値型の参照型への変換、すなわちボックス化です。

コード:
fi.SetValue(p, "ABC")


ここと、

コード:
MsgBox(fi.GetValue(p))


ここでpを2度ボックス化しています。そのため、最初と2回目では対象となるオブジェクトが変わってしまい、最初のコードで設定したはずの値が反映されていないように見えるのです。実際には操作対象が変わってしまっているのが原因です。

そこで、SetValueメソッドを呼び出す前に、次のコードでボックス化をしてしまいます。

コード:
Dim o As Object = p


そして、SetValue、GetValueは両方ともpではなくoに対して呼び出します。そうすると、同じオブジェクトに対して操作が行われるので、ABCが表示されるはず・・・でした。実際、同じコードをC#で書き直して、この戦略をとれば、ABCが表示されるのです。

ですが、VB.NETは私たちの上を行っていました。VB.NETでは、オブジェクトを引数にしてメソッドを呼び出すと、System.Runtime.CompilerServices.RuntimeHelpers.GetObjectValueメソッドを呼び出して、対象のオブジェクトが実は値型をボックス化したものではないのか?ということを調査しています。そうだった場合はあらためてボックス化を行ってくれちゃいます。そういうわけで、C#とは異なる動作をします。

VB.NETがGetObjectValueを呼び出す理由は、値型のインスタンスを扱っている場合、ボックス化されていようといまいと同じ動作をすべきだから、ということのようです。
mei
大ベテラン
会議室デビュー日: 2003/04/08
投稿数: 114
投稿日時: 2003-04-08 22:11
C#だと、ボクシングすると出来るみたいです。

using System;
using System.Reflection;

struct Person {
public int Age;
public string Name;
}

class Class1
{
[STAThread]
static void Main(string[] args)
{
Object p = new Person();
Type t = p.GetType();
FieldInfo fi = t.GetField("Name");
fi.SetValue(p, "ABC");
Console.WriteLine("{0}", fi.GetValue(p));
}
}

こんな感じで、ABCと表示されました。
ところが、VB.NETで等価な(つもりの)コードを書いても結果が異なります。

Imports System
Imports System.Reflection

Module Module1
Public Structure Person
Public Age As Integer
Public Name As String
End Structure

Sub Main()
Dim p As Object = New Person()
Dim t As Type = p.GetType()
Dim fi As FieldInfo = t.GetField("Name")
fi.SetValue(p, "ABC")
Console.WriteLine("{0}", fi.GetValue(p))

End Sub

End Module

結果はブランク。
不思議に思ってVBで作ったモジュールを逆コンパイルしてみたところ、

[STAThread]
public static void Main() {
FieldInfo local0;
object local1;
Type local2;
Person local3;

<{ class ILEngineer:ps::MSIL::InitObj }>;
local1 = local3;
local2 = local1.GetType();
local0 = local2.GetField("Name");
local0.SetValue(RuntimeHelpers.GetObjectValue(local1), "ABC");
Console.WriteLine("{0}", RuntimeHelpers.GetObjectValue(local0.GetValue(RuntimeHelpers.GetObjectValue(local1))));
}

と、なっていまして、
C#に比べて、RuntimeHelpers.GetObjectValueが余計でした。
VBとC#って微妙に結果が変わることもあるのですねぇ〜
#それとも、VB側で別の書き方があるのでしょうか・・・
mei
大ベテラン
会議室デビュー日: 2003/04/08
投稿数: 114
投稿日時: 2003-04-08 22:14
NothingButXMLInfoSetさんと、ほとんど一緒の内容ですね(汗)
タイミング的にかぶってしまった・・・
やぎ
会議室デビュー日: 2003/04/01
投稿数: 14
投稿日時: 2003-04-09 20:46
皆さん、ありがとうございます。

C#だとボックス化するとできるんですね。
VB.NETでできればベストだったんですが、仕方ないですね。
その部分だけC#でクラス化して使おうと思います。

しかし、今後それがC#のバグだったということで、VB.NETの
ような仕様にはならないですよね?
ちょっと心配です。
1

スキルアップ/キャリアアップ(JOB@IT)