【问题标题】:c# Iterative reflectionc# 迭代反射
【发布时间】:2015-05-18 23:09:16
【问题描述】:

我想使用反射设置值,但使设置函数能够访问多个子对象。当子对象是类时我没有问题,但是对于结构它不起作用。

例如,我有以下类和结构

class MyConfig
{
    Gravity gravity;
}
struct Gravity
{
    Vector2 direction;
}
struct Vector2
{
    float X,Y;
}

我希望能够像这样设置值:

MyConfig cfg=new MyConfig();
setValueViaReflection (cfg,"gravity.direction.X",76.5f);

显然应该将 cfg.gravity.direction.X 设置为 76.5f

我现在的代码是这样的:

void setValueViaReflection (object obj,string fieldName,object value)
{
int i;
TypeInfo baseType=null;
FieldInfo field;

    string []split=fieldName.Split ('.');

    // get one subobject in each iteration
    for (i=0;i<split.Count()-1;i++)
    {
        string fname=split[i];
        baseType=obj.GetType().GetTypeInfo();

        field=baseType.GetDeclaredField (fname);
        if (field==null) return;

        obj=field.GetValue (obj);
        if (obj==null) return;

    }

    // finally you've got the final type, set value
    baseType=obj.GetType().GetTypeInfo();

    field=baseType.GetDeclaredField (split[split.Count()-1]);
    if (field==null) return;

    field.SetValue (obj,value);
}

我知道我应该使用SetValueDirect,但使用它没有区别(值在“obj”中修改,但似乎是一个值类型,因此它不会改变原始对象。

我认为问题出在 field.GetValue 中,它创建了一个值类型,使最终的 SetValueDirect 无用。

如果将 Gravity 和 Vector2 设置为类,则代码运行良好。

【问题讨论】:

标签: c# reflection struct


【解决方案1】:

您的分析基本正确。也就是说,问题源于值类型的使用。基本问题是,当您修改值类型的实例时,不会影响该实例的原始副本。它只会更改您从原始存储中检索到的当前副本(在本例中为字段)。要使更改对原始存储产生影响,您必须修改当前副本,然后将该副本存储回该存储。

当然,在遍历字段值的路径时,这意味着在每一步,您都需要修改当前值并将其存储回来,至少对于值类型而言。对于引用类型字段,这在技术上是不必要的——修改该存储值中的字段确实更新原始存储——但将旧值(即对对象的引用)存储回它的存储空间。

对我来说,这个问题似乎更容易递归思考。 IE。您已经知道解决基本情况很容易,因为框架通过 GetValue()SetValue() 方法直接为您提供了该机制。

因此,如果您能够以某种方式将问题逐步减少到基本情况,那么您已经解决了主要问题。请注意,这种减少的一个关键方面是,在解决了基本情况后,您需要将该结果传播到字段链;这意味着中间结果,因此这里不仅涉及递归,而且不能转换为简单的迭代解决方案(即它不是“尾递归”)。

换句话说,递归不仅是解决问题的一种更简单的方法,迭代解决方案仍然需要递归的某些方面(即行为类似于堆栈的数据结构)。所以你不妨使用递归,因为它更紧凑,更容易编写(恕我直言,这是一种更容易思考问题的方法)。

这是一个递归方法,可以满足您的需求:

static void SetValueByPath(object target, string path, object value)
{
    int dotIndex = path.IndexOf('.');
    string targetProperty = dotIndex > 0 ?
        targetProperty = path.Substring(0, dotIndex) : path;
    FieldInfo fieldInfo = target.GetType().GetTypeInfo().GetDeclaredField(targetProperty);

    if (dotIndex > 0)
    {
        object currentValue = fieldInfo.GetValue(target);

        SetValueByPath(currentValue, path.Substring(dotIndex + 1), value);

        value = currentValue;
    }

    fieldInfo.SetValue(target, value);
}

请注意,当值类型字段发生装箱时,运行时会正确处理它们。可以修改装箱值类型中的字段,并且不会创建值类型的新副本;该字段在原始引用的装箱值中更新(即上述代码中的currentValue)。

还要注意,即使混合中有引用类型字段,上述代码也能正常工作。例如。如果Gravity 类型是class。如果您真的关心,可以更改上面的代码以跳过将中间引用类型值复制回其字段,因为在这种情况下字段的值本身不会改变,但这只会使代码复杂化受益。

【讨论】:

  • 我昨天在睡觉前尝试了一个递归解决方案并开始工作,但是您的解决方案紧凑、简洁,而且解释是一流的:)(我仍然是一个反思新手)非常感谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-01-13
  • 2021-04-15
  • 2011-07-31
  • 2013-11-21
  • 2011-04-06
  • 1970-01-01
  • 2010-11-29
相关资源
最近更新 更多