c#迭代反射 [英] c# Iterative reflection

查看:34
本文介绍了c#迭代反射的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I want to set values using reflection, but making the setting function able to access several subobjects. I'm having no problems when subobjects are classes, but with structs it's not working.

例如,我有以下类和结构

In example, I have the following class and structs

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

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

I'd like to be able to set values like this:

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

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

which should obviously set cfg.gravity.direction.X to 76.5f

我现在的代码是这样的:

My code right now is this:

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"中被修改,但似乎是一个值类型,因此它不会改变原始对象.

I'm aware that I should use SetValueDirect, but using it makes no difference (the value is modified in "obj", but seem to be a value type, thus it's not changing the original object.

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

I think the problem is in field.GetValue which creates a value type, making the final SetValueDirect useless.

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

The code works well if Gravity and Vector2 are set as classes.

推荐答案

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

Your analysis is fundamentally correct. That is, the problem stems from the use of value types. The basic issue is that when you modify an instance of a value type, that has no effect on the original copy of that instance. It only changes your current copy, which you retrieved from the original storage (in this case, a field). For the change to have an effect on that original storage, you would have to modify the current copy, and then store that copy back to that storage.

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

Of course, when traversing a path of field values, that means that at each step, you need to modify the current value and store it back, at least for value types. For reference type fields, this is technically not necessary — modifying a field in that storage's value does update the original storage — but there's no harm in storing the old value (i.e. the reference to the object) back to its storage.

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

To me, this problem seems much easier to think about recursively. I.e. you already know it's easy to solve the base case, as the framework provides that mechanism for you directly with the GetValue() and SetValue() methods.

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

So if you can somehow reduce the problem in steps to that base case, you have solved the main problem. Note that a key aspect of this reduction is that having solved the base case, you need to propagate that results back up the chain of fields; this implies intermediate results, so not only is recursion implicated here, it won't be convertible to a plain iterative solution (i.e. it's not "tail recursion").

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

In other words, not only is recursion an easier way to approach the problem, an iterative solution is still going to require some aspects of recursion (i.e. a data structure that behaves like a stack). So you might as well use recursion, since it's more compact and easier to write (and IMHO an easier way to think about the problem).

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

Here is a recursive method that does what you want:

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

Note that while boxing is occurring with the value type fields, the runtime does the right thing with them. You can modify a field in a boxed value type, and no new copy of the value type is made; the field is updated in the original referenced boxed value (i.e. currentValue in the above code).

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

Note also that the above code works fine even if there are reference type fields in the mix. E.g. if the Gravity type were a class. If you really cared, it would be possible to change the above code to skip copying an intermediate reference type value back to its field, since the field's value itself wouldn't have changed in that case, but that just complicates the code for no real benefit.

这篇关于c#迭代反射的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆