C# 反射 - 根据属性路径设置值

3
我想通过指定一个由点分隔的路径来更新任何公共属性中的值。
但是每当我调用我的方法时,都会在以下行中收到错误:
pi.SetValue(instance, value1, null);

错误信息:
对象与目标类型不匹配。
我的方法:
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<>));
}

1
为什么当你有一个嵌套属性时,你认为t.GetProperty适用于原始对象类型?所以当instanceMyType类型并且有一个类型为PropType的属性MyProperty时, t将总是评估为MyType,而t.GetProperty(pp [1])返回null,因为pp [1]PropType中的一个属性,而不是MyType中的一个属性。您需要一个递归方法,仅评估当前级别。 - MakePeaceGreatAgain
@HimBromBeere 在循环中滚动时更新了t,它不总是原始值。尽管如此,我仍然认为递归评估是更合适的解决方案。 - Bradley Uffner
顺便问一下:当任何中间属性未设置(null)时,您想要做什么? - MakePeaceGreatAgain
@BradleyUffner 啊,我确实在OP的代码中错过了那个点。 - MakePeaceGreatAgain
我认为这最终会尝试设置“过深的一个级别”的值。 - Bradley Uffner
2个回答

3

我认为你的原始版本最终会将值“设置得太深了一层”。

我觉得使用递归模式会更容易理解,需要的代码也较少。以下是我快速搭建的一个简单测试案例的版本。

有几个优化机会(在递归调用上重新构建字符串),以及一些边缘情况(例如null检查),由于时间的原因,我现在无法处理,但我认为它们不会太难添加。

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

这里是一个展示LINQPad的演示链接
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";
}

它产生以下结果: enter image description here

我认为您忘记了:Convert.ChangeType。添加类型转换后,它可以正常工作。非常感谢! - g_m
我有点困惑,为什么您需要在设置值之前将其转换。除非您试图传递与属性值不同类型的“setTo”,否则不应该需要转换。在我的示例中,“D”属性是一个“string”,而“setTo”是一个“string”,不需要转换。唯一需要转换的方式是如果您尝试将像“string”这样的内容传递到一个“int”属性中,但这似乎是一个非常奇怪的事情。 - Bradley Uffner
如果您正在处理配置文件之类的东西,其中值始终以字符串形式存储,并且这些值需要推入在调用“SetValue”时可能不知道类型的属性中,那么这样做可能是有意义的。 - Bradley Uffner
类型通常不匹配,如果值将由用户输入,例如在文本字段中。顺便说一句,干得好! - g_m
1
如果是这种情况,你应该意识到你的转换方法可能存在问题。Convert.ChangeType 有时会在转换字符串时出现问题。更可靠的方法是使用 var converter = TypeDescriptor.GetConverter(targetType); 获取 TypeConverter,然后调用 converter.ConvertFromString 进行转换。 - Bradley Uffner

0

我想完成答案 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);
    }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接