Search code examples
c#reflectionpropertyinfo

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

Solution

  • 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