我正在尝试解决以下问题。我们有一个任意结构的对象和一个表示字段名称的字符串数组。此数组是用于使用反射检索特定字段的路径。然后有一个值应存储在最终字段中。例如,考虑以下类层次结构:
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#中执行此操作?
当路径为空时,我还需要支持边缘情况,在这种情况下,需要为根对象本身分配不同的值。
编辑:我的代码实际上使用更复杂的方法来检索成员。路径中的条目不一定是字段名称,它们也可以是属性名称,数组或列表中的索引,字典中的键等。因此包装它的类将很复杂。我正在寻找一个更简单的解决方案。
答案 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));
创建对象需要更多的工作,但效果相同。
您可以采取的另一种方法是创建FieldWrapper
,PropertyWrapper
,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);