As you suspected, a value of type short assigned to a property of type int through reflection, although typeof(int).IsAssignableFrom(typeof(short)) returns false .
Looking at the stack of 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 ...
An exception is RuntimeType.TryChangeType 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 whether to throw an ArgumentException . I suspect that primitive conversion is being processed somewhere around this function:
There is another interesting code in System.RuntimeType.CheckValue(object, Binder, CultureInfo, BindingFlags) :
bool flag = base.IsPointer || this.IsEnum || base.IsPrimitive; if (flag) { ... if (RuntimeType.CanValueSpecialCast(valueType, this)) { ... } }
So it looks like pointer, enumeration, and primitive types are handled differently. I let you try to copy this logic. It would be easier to just use IsAssignableFrom and handle the whole types separately. Handling pointer, enumeration, and primitive type looks very complicated, I will just step back from try-catch there. However, you can cache if an argument error occurs, so subsequent calls to the same method with the same parameters may be somewhat faster.