在任意对象结构中设置深度字段

时间:2013-04-02 16:17:33

标签: c# reflection reference

我正在尝试解决以下问题。我们有一个任意结构的对象和一个表示字段名称的字符串数组。此数组是用于使用反射检索特定字段的路径。然后有一个值应存储在最终字段中。例如,考虑以下类层次结构:

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#中执行此操作?

当路径为空时,我还需要支持边缘情况,在这种情况下,需要为根对象本身分配不同的值。

编辑:我的代码实际上使用更复杂的方法来检索成员。路径中的条目不一定是字段名称,它们也可以是属性名称,数组或列表中的索引,字典中的键等。因此包装它的类将很复杂。我正在寻找一个更简单的解决方案。

3 个答案:

答案 0 :(得分: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对象,您可以获取并设置该对象的值。这允许您传递FieldWrapper的实例,只是获取/设置属性并使其影响构造函数中提供的对象的基础字段。

如果你需要更通用的东西,你可以依赖闭包:

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));

创建对象需要更多的工作,但效果相同。

您可以采取的另一种方法是创建FieldWrapperPropertyWrapper,DictionaryWrapper , etc. and have them all implement an IWrapper interface that exposes a Value`,这样一旦您创建了包装器,就可以了不关心底层实现是什么。为每种类型的对象创建包装器需要更多的工作,但最终花费更少的时间来创建包装类型的每个实例。

答案 1 :(得分: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()

答案 2 :(得分: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);