0

次の問題を解決しようとしています。任意の構造のオブジェクトと、フィールド名を表す文字列の配列があります。この配列は、リフレクションを使用して特定のフィールドを取得するために使用されるパスです。次に、最終フィールドに格納する必要がある値があります。たとえば、次のクラス階層を考えてみましょう。

class A {
  public int i;
}

class B {
  public A a;
}

class C {
  public B b;
}

class D {
  public C c;
}

どういうわけか入力を取得するとしましょう:

object obj = GetObject();              // e.g. returns object of type D
List<string> path = GetPathToStore();  // e.g. returns {"c", "b", "a", "i"}
object value = GetValueToBeStored();   // e.g. returns 42

次のループを書きました。

foreach (string fieldName in path) {
  FileInfo fieldInfo = obj.GetType().GetField(fieldName);
  obj = fieldInfo.GetValue(obj);
}

次に、次のようなものがあればいいでしょう。

obj = value;

ただし、これは参照のみを変更し、オブジェクトの実際のフィールドは変更しません。C++ では、次のように記述します。

*obj = value;

しかし、C#でこれを行う方法は?

また、ルート オブジェクト自体に別の値を割り当てる必要があるパスが空の場合のエッジ ケースもサポートする必要があります。

編集:私のコードは、実際にはより複雑なアプローチを使用してメンバーを取得します。パスのエントリは必ずしもフィールド名ではなく、プロパティ名、配列またはリストのインデックス、ディクショナリのキーなどである場合もあります。したがって、それをラップするクラスは複雑になります。もっと簡単な解決策を探しています。

4

3 に答える 3

1

多分このようなもの:

object obj = new D { c = new C { b = new B { a = new A { i = 1 } } } };
List<string> path = new List<string> { "c", "b", "a", "i" };
object value = 42;

FieldInfo fieldInfo = null;
object prevObj = null;
object obj2 = obj;
for (int i = 0; i < path.Count; i++)
{
    string fieldName = path[i];
    fieldInfo = obj2.GetType().GetField(fieldName);
    if (i == path.Count - 1) prevObj = obj2;
    obj2 = fieldInfo.GetValue(obj2);
}
if (fieldInfo != null)
{
    fieldInfo.SetValue(prevObj, value);
}
Console.WriteLine(((D)obj).c.b.a.i == (int) value);
于 2013-04-02T16:39:36.793 に答える
1

ポインター操作と同じように、マネージ言語に間接的なレイヤーを追加できます。一般に、クラスは一般にオブジェクトへのポインターと考えることができるため、これは通常、新しいクラスを使用して行われます。

public class FieldWrapper
{
    private object obj;
    private FieldInfo field;
    public FieldWrapper(object obj, FieldInfo field)
    {
        this.obj = obj;
        this.field = field;
    }

    public object Value
    {
        get
        {
            return field.GetValue(obj);
        }
        set
        {
            field.SetValue(obj, value);
        }
    }
}

オブジェクト インスタンスとオブジェクトを保持することFieldInfoで、そのオブジェクトの値を取得および設定できます。これにより、FieldWrapperaround のインスタンスを渡し、プロパティを取得/設定するだけで、コンストラクターで提供されるオブジェクトの基になるフィールドに影響を与えることができます。

より一般的なものが必要な場合は、クロージャーに頼ることができます。

public class Wrapper
{
    private Func<object> getter;
    private Action<object> setter;
    public Wrapper(Func<object> getter, Action<object> setter)
    {
        this.getter = getter;
        this.setter = setter;
    }

    public object Value
    {
        get
        { return getter(); }
        set
        { setter(value); }
    }
}

次に、それを使用するには、次のようなことができます。

Wrapper pointer = new Wrapper(()=> fieldInfo.GetValue(obj)
    , value =>  fieldInfo.SetValue(obj, value));

オブジェクトを作成するにはもう少し手間がかかりますが、効果は同じです。

もう 1 つの方法はFieldWrapperPropertyWrapperDictionaryWrapper , etc. and have them all implement anIWrapper interface that exposes aValue` を作成して、ラッパーを作成したら、基になる実装が何であるかを気にしないようにすることです。各タイプのオブジェクトのラッパーを作成する前に、もう少し作業が必要ですが、ラップされたタイプの各インスタンスを作成する時間が短縮されます。

于 2013-04-02T16:23:28.457 に答える
1

あなたの質問を正しく理解しているかどうかはわかりませんが、次のことを望んでいると思います。

var fieldInfo = obj.GetType().GetField(fieldName);
fieldInfo.SetValue(obj, newValue);

編集:フィールドだけでなくプロパティもサポートするには、次を試してください:

foreach(var memberInfo in obj.GetType().GetMember(memberName))
{
    if(memberInfo is FieldInfo)
    {
        ((FieldInfo)memberInfo).SetValue(obj, newValue);
    }
    else if(memberInfo is PropertyInfo)
    {
        ((PropertyInfo)memberInfo).SetValue(obj, newValue);
    }
    // etc ...
}

ただし、インデックスをどのように正確に処理したいかはわかりません。インデックス付きプロパティのインデックスを に渡すことができますPropertyInfo.SetValue()

于 2013-04-02T16:23:44.193 に答える