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

您可以在托管语言中添加额外的间接层,就像通过指针操作一样。通常,这通常是通过使用新类来完成的,因为您通常可以将类视为指向对象的指针。

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

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

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

于 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 回答
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 回答