检查PropertyInfo.SetValue是否会抛出ArgumentException

时间:2015-12-23 10:01:51

标签: c# reflection type-conversion

我继承了一些尝试设置属性的代码:

object target = ...    // some instance on which we want to set a property
object value = ...     // some value - in this case, a string
var propertyInfo = ... // some property of target - in this case, not a string

try
{
    propertyInfo.SetValue(obj, value, null);
}
catch (ArgumentException)
{
    // We go off and look for our own way of converting between
    // the type of value and the type of the property.
}

在目前的使用中,异常会被捕获并抛出很多,所以我想首先检查一下:

if (propertyInfo.PropertyType.IsAssignableFrom(value.GetType())
{
    // Try/catch as above
}
else
{
    // Do the manual conversion as if the exception had been thrown.
}

这样运行得更快。但是,我唯一担心的是,IsAssignableFrom可能会为某些类型返回false,其中SetValue实际上成功。 (这会导致我们在不需要时查找手动转换,并且可能无法完全设置值。)

SetValue的规范说

  

value 无法转换为PropertyType

的类型

与可转让性不完全相同。

(如果IsAssignableFromtrue失败的情况下返回SetValue,那很好 - 手动转换仍然会发生。)

有人可以向我确认这是否可行?

1 个答案:

答案 0 :(得分:2)

如您所料,short类型的值可通过反射分配给int类型的属性,但typeof(int).IsAssignableFrom(typeof(short))会返回false

查看ArgumentException

的堆栈跟踪
  at System.RuntimeType.TryChangeType(Object value, Binder binder, CultureInfo culture, Boolean needsSpecialCast)
  at System.RuntimeType.CheckValue(Object value, Binder binder, CultureInfo culture, BindingFlags invokeAttr)
  at System.Reflection.MethodBase.CheckArguments(Object[] parameters, Binder binder, BindingFlags invokeAttr, CultureInfo culture, Signature sig)
  at System.Reflection.RuntimeMethodInfo.InvokeArgumentsCheck(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
  at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
  at System.Reflection.RuntimePropertyInfo.SetValue(Object obj, Object value, BindingFlags invokeAttr, Binder binder, Object[] index, CultureInfo culture)
  at System.Reflection.RuntimePropertyInfo.SetValue(Object obj, Object value, Object[] index)
  at System.Reflection.PropertyInfo.SetValue(Object obj, Object value)
  at ConsoleApplication3.Program.Main(String[] args) in ...

RuntimeType.TryChangeType引发了异常。查看mscorlib中的源代码:

// System.RuntimeType
[SecurityCritical]
private object TryChangeType(object value, Binder binder, CultureInfo culture, bool needsSpecialCast)
{
    ...
    if (this.IsInstanceOfType(value))
    {
      return value;
    }
    ...
      if (RuntimeType.CanValueSpecialCast(valueType, this))
      {
    ...
  }
  throw new ArgumentException(string.Format(CultureInfo.CurrentUICulture, Environment.GetResourceString("Arg_ObjObjEx"), value.GetType(), this));
}

不幸的是,调用了许多内部函数来确定是否应该抛出ArgumentException。我怀疑原语的转换是在这个函数的某处处理的:

// System.RuntimeType
[SecurityCritical]
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern bool CanValueSpecialCast(RuntimeType valueType, RuntimeType targetType);

System.RuntimeType.CheckValue(object, Binder, CultureInfo, BindingFlags)中还有一些有趣的代码:

bool flag = base.IsPointer || this.IsEnum || base.IsPrimitive;
if (flag)
{
  ...
  if (RuntimeType.CanValueSpecialCast(valueType, this))
  {
    ...
  }
}

因此看起来像指针,枚举和基本类型以不同的方式处理。我不鼓励你试着复制那个逻辑。使用IsAssignableFrom可能更容易,并分别处理整数类型。处理指针,枚举和原始类型参数看起来非常复杂,我只会回到try-catch那里。但是,如果发生参数错误,您可以缓存,因此对具有相同参数类型的相同方法的后续调用可能会更快。