Collectives™ on Stack Overflow

Find centralized, trusted content and collaborate around the technologies you use most.

Learn more about Collectives

Teams

Q&A for work

Connect and share knowledge within a single location that is structured and easy to search.

Learn more about Teams

I have inherited some code that tries to set a property:

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
    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.

In current usage the exception is caught and thrown a lot, so I would like to make a check first:

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

This runs much faster. However, my one concern is that IsAssignableFrom could return false for some pair of types where SetValue would in fact succeed. (This would cause us to look for the manual conversion when we don't need to, and possibly fail to set a value altogether.)

The spec for SetValue says

value cannot be converted to the type of PropertyType

which is not exactly the same as assignability.

(If IsAssignableFrom returns true in cases where SetValue fails, that's fine - the manual conversion will still happen.)

Can someone confirm for me whether this is possible or not?

Please give an actual example where this happens. Presumably you have one, so it should be really easy to give a short but complete example which logs IsAssignableFrom and successfully calls SetValue. (This may only be an issue with primitives, which could be reasonably easily detected...) – Jon Skeet Dec 23, 2015 at 10:04 @Jon The case I see failing a lot is trying to assign a string to a property of a custom type that is not assignable from string (but for which we have a custom converter). However, this code could be setting any type to any other type and I don't want to break it, so I'm interested in the general case. – Rawling Dec 23, 2015 at 10:12 Well I'm surprised that SetValue would call a custom converter - but again, a complete example would make that clear. – Jon Skeet Dec 23, 2015 at 10:13 @Jon SetValue doesn't call the converter. We go and look for one if SetValue fails. I don't really think it's important what we do if it fails - I just need to know whether there are cases it disagrees with IsAssignableFrom. – Rawling Dec 23, 2015 at 10:15 What's important is that you're claiming SetValue would succeed when called with a type which cannot be converted to the property type. That seems unusual to me, so I've asked for an example. I'm not sure why you're reluctant to provide such an example. (It's not clear what you mean by "This would cause us to look for a TypeConverter" - what would? Where is that code? I would expect SetValue to fail and then you can look for a type converter...) – Jon Skeet Dec 23, 2015 at 10:17

As you suspected, a value of type short is assignable to a property of type int through reflection, although typeof(int).IsAssignableFrom(typeof(short)) returns false.

Looking at the stacktrace of the 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 ...

The exception is thrown in RuntimeType.TryChangeType. Looking at the source in 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));

Unfortunately there are many internal functions called to determine if an ArgumentException should be thrown. I suspect that the conversion of primitives are handled somewhere around this function:

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

There is some interesting code in System.RuntimeType.CheckValue(object, Binder, CultureInfo, BindingFlags) as well:

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

So it looks like pointer, enum and primitive types are handled in a different way. I discourage you to try to copy that logic. It might be easier to just use IsAssignableFrom, and handle integral types separately. Handling pointer, enum and primitive type arguments looks very complicated, I would just fallback to try-catch there. However, you can cache if an argument error happened so subsequent calls on the same method with same parameter types might be somewhat faster.

Thanks, hege. I'd just found the long/int example myself but you've done much better at finding where and what the logic is than I have, and the caching idea is great - I'll probably end up going down that route. – Rawling Dec 23, 2015 at 11:55

Thanks for contributing an answer to Stack Overflow!

  • Please be sure to answer the question. Provide details and share your research!

But avoid

  • Asking for help, clarification, or responding to other answers.
  • Making statements based on opinion; back them up with references or personal experience.

To learn more, see our tips on writing great answers.