g_m
g_m

Reputation: 526

C# Reflection - SetValue by path to property

I'd like to update value in any public property by specifying a dots delimited path to it.

But whenever I call my method I receive an error in line:

pi.SetValue(instance, value1, null);

Error message:

Object does not match target type.

My method:

private void SetPathValue(object instance, string path, object value)
{
    string[] pp = path.Split('.');
    Type t = instance.GetType();
    for (int i = 0; i < pp.Length; i++)
    {
        PropertyInfo pi = t.GetProperty(pp[i]);
        if (pi == null)
        {
            throw new ArgumentException("Properties path is not correct");
        }
        else
        {
            instance = pi.GetValue(instance, null);
            t = pi.PropertyType;
            if (i == pp.Length - 1)//last
            {
               // Type targetType = IsNullableType(pi.PropertyType) ? Nullable.GetUnderlyingType(pi.PropertyType) : pi.PropertyType;
                var value1 = Convert.ChangeType(value, instance.GetType());
                pi.SetValue(instance, value1, null);//ERROR
            }
        }
    }
}

private static bool IsNullableType(Type type)
{
    return type.IsGenericType && type.GetGenericTypeDefinition().Equals(typeof(Nullable<>));
}

Upvotes: 3

Views: 2385

Answers (2)

Alex Horban
Alex Horban

Reputation: 1

I want to complete the answer Bradley Uffner

public void SetProperty (object target, string property, object setTo)
{
  var parts = property.Split ('.');
  // if target object is List and target object no end target - 
  // we need cast to IList and get value by index
  if (target.GetType ().Namespace == "System.Collections.Generic"
      && parts.Length != 1)
    {
      var targetList = (IList) target;
      var value = targetList[int.Parse (parts.First ())];
      SetProperty (value, string.Join (".", parts.Skip (1)), setTo);
    }
  else
    {
      var prop = target.GetType ().GetProperty (parts[0]);
      if (parts.Length == 1)
    {
      // last property
      prop.SetValue (target, setTo, null);
    }
      else
    {
      // Not at the end, go recursive
      var value = prop.GetValue (target);
      SetProperty (value, string.Join (".", parts.Skip (1)), setTo);
    }
    }
}

Upvotes: 0

Bradley Uffner
Bradley Uffner

Reputation: 16991

I think your original version will end up setting the value "one level too deep".

I think a recursive pattern would be easier to follow, and require less code. Here is a quick version that I threw together that works on simple test cases.

There are several opportunities for optimizations (rebuilding the string on the recursive call), and edge cases (like null checks) that I don't have the time to handle right now, but I don't think they will be too hard to add.

public void SetProperty(object target, string property, object setTo)
{
    var parts = property.Split('.');
    var prop = target.GetType().GetProperty(parts[0]);
    if (parts.Length == 1)
    {
        // last property
        prop.SetValue(target, setTo, null);
    }
    else
    {
        // Not at the end, go recursive
        var value = prop.GetValue(target);
        SetProperty(value, string.Join(".", parts.Skip(1)), setTo);
    }
}

Here is a LINQPad demo showing it in action:

void Main()
{
    var value = new A();
    Debug.WriteLine("Original value:");
    value.Dump();

    Debug.WriteLine("Changed value:");
    SetProperty(value, "B.C.D","changed!");
    value.Dump();
}

public void SetProperty(object target, string property, object setTo)
{...}

public class A
{
    public B B { get; set; } = new B();
}

public class B
{
    public C C { get; set; } = new C();
}

public class C
{
    public string D { get; set; } = "test";
}

It produces the following results:

enter image description here

Upvotes: 3

Related Questions