【问题标题】:C# Reflection - SetValue by path to propertyC# 反射 - 通过属性路径设置值
【发布时间】:2018-12-14 11:39:38
【问题描述】:

我想通过指定点分隔路径来更新任何公共属性中的值。

但是每当我调用我的方法时,我都会收到一条错误消息:

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

【问题讨论】:

  • 当你有一个嵌套属性时,为什么你认为t.GetProperty 对你的原始对象的类型起作用?因此,当instance 是MyType 类型并且具有PropType 类型的属性MyProperty 时,t 将始终计算为MyType,并且t.GetProperty(pp[1]) 返回null,因为pp[1] 是其中的一个属性PropType,不在MyType 内。您需要一个仅评估当前级别的递归方法。
  • @HimBromBeere 他在循环中更新t,它并不总是原始值。我仍然同意递归评估是一个更合适的解决方案。
  • 顺便说一句:如果没有设置任何中间属性(null),你想做什么?
  • @BradleyUffner Aaah,我确实错过了 OPs 代码中的这一点。
  • 我认为这最终会尝试将值设置为“太深一层”。

标签: c# reflection propertyinfo


【解决方案1】:

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

我认为递归模式更容易遵循,并且需要更少的代码。这是我整理的一个快速版本,适用于简单的测试用例。

有几个优化机会(在递归调用中重建字符串)和边缘情况(如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);
    }
}

Here 是一个 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";
}

它产生以下结果:

【讨论】:

  • 我想,你错过了:Convert.ChangeType。添加类型转换后,它可以正常工作。非常感谢!
  • 我有点困惑为什么你需要在设置之前转换你的值。除非您尝试传递与属性值不同类型的 setTo,否则不需要它。在我的示例中,D 属性是 string,setTo 是 string,不需要转换。唯一需要转换的方法是,如果您尝试将 string 之类的内容传递给 int 的属性,但这似乎是一件非常奇怪的事情。
  • 我想如果您将其用于处理配置文件之类的事情,那将是有意义的,其中值 总是 存储为字符串,并且这些值需要被推入到您致电SetValue 时可能不知道其类型的属性。
  • 如果值将由用户输入,则类型通常不匹配,例如在文本字段中。顺便说一句,干得好!
  • 如果是这种情况,您应该意识到您的转换方法可能存在问题。 Convert.ChangeType 有时会在转换字符串时遇到问题。更可靠的方法是使用var converter = TypeDescriptor.GetConverter(targetType);获取TypeConverter,然后调用converter.ConvertFromString进行转换。
【解决方案2】:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-02-07
    • 1970-01-01
    • 2019-02-28
    • 1970-01-01
    • 1970-01-01
    • 2010-11-08
    • 1970-01-01
    相关资源
    最近更新 更多