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
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.
}
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
}
else
{
// 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?
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With