1

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

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

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.

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

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

KakCAT
  • 296
  • 1
  • 14
  • I believe you will have to explicitly box the struct variables. [Related answer](http://stackoverflow.com/a/13308275/147613) – Amit May 18 '15 at 23:21
  • You have to set the value returned from GetValue on the parent (if it is a value type). – Brannon May 19 '15 at 00:11
  • This is one of the many reasons of why you should avoid mutable value type. – Servy May 19 '15 at 18:32
  • This post may be helpful for you http://stackoverflow.com/questions/13307998/access-fields-of-a-struct-in-an-object-with-reflection – Nazmul May 19 '15 at 19:11

1 Answers1

1

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.

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

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

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.

Peter Duniho
  • 68,759
  • 7
  • 102
  • 136
  • I tried a recursive solution yesterday before going to bed and worked, but your solution is compact, cleaner and the explanation is top notch :) (I'm still a reflection newbie) Thanks a lot! – KakCAT May 20 '15 at 07:44