2013-04-02 52 views
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#中做到这一点?

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

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

+0

BTW:这是C#编码约定不使用除'readonly'值公共字段。改用属性。它们很便宜: 'public PropTypeA PropA {get;组; }' – chris

+0

我不会设计类,但是我的代码同时支持字段和属性。这只是它的一个简化版本来演示这个问题。不过谢谢你的提示。 –

回答

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

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

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

+0

请看看问题中的编辑。 –

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

+0

这是可以接受的,如果我只有字段,但我支持属性,数组索引等。请看看问题中的编辑。 –

+0

然后你需要使用'GetMember()'而不是'GetField()'。一探究竟。 – chris