Reputation: 73351
I want to use reflection and do either an implicit or explicit coversion using reflection.
Given I have defined Foo this way
public class Foo
{
public static explicit operator decimal(Foo foo)
{
return foo.Value;
}
public static explicit operator Foo(decimal number)
{
return new Foo(number);
}
public Foo() { }
public Foo(decimal number)
{
Value = number;
}
public decimal Value { get; set; }
public override string ToString()
{
return Value.ToString();
}
}
When I run this code
decimal someNumber = 42.42m;
var test = (Foo)someNumber;
Console.WriteLine(test); // Writes 42.42 No problems
When I try to define a class with Foo as a member type and use reflection to set it. I get the following Exception.
Error : Object of type 'System.Decimal' cannot be converted to type 'Foo'.
StackTrace: 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.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture, Boolean skipVisibilityChecks)
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)
Here is the code I use to set the property with reflection
public class FooComposite
{
public Foo Bar { get; set; }
}
var properties = typeof(FooComposite).GetProperties();
var testFoo = new FooComposite();
foreach(var propertyInfo in properties)
{
propertyInfo.SetValue(testFoo, 17.17m, null); // Exception generated on this line
}
Console.WriteLine(testFoo.Bar); // Never gets here
How can I do this conversion?
Upvotes: 15
Views: 7953
Reputation: 41
Anything can be done by reflection.
My problem was a reflection method trying to make a deep copy between two classes because of a complex type. I tried to define an explicit operator
conversion, but it didn't seem to get called, so I figured out a way to get it by reflection. Using some other research about calling static methods, I found this works for me when copying a complex type stored in pSource into a different type in property pDest. the type in pDest has a conversion from pSource's type.
MethodInfo[] static_methods = pDest.PropertyType.GetMethods(System.Reflection.BindingFlags.Static | BindingFlags.Public);
if (static_methods != null)
{
foreach (MethodInfo method in static_methods)
{
if(method.Name== "op_Explicit") // this is a constant
{ // for explicit operators
ParameterInfo[] paramSet = method.GetParameters();
if ((paramSet != null) && (paramSet.Length == 1))
{
if (paramSet[0].ParameterType == pSource.PropertyType) // match the types!
{
pDest.SetValue( // Destination prop
dstVar, // Destination instance
method.Invoke( // converter method
null, // static has no 'this'
new object[] { // value to convert from
pSource.GetValue(source, null)
} // source property on
// source instance
)
); // SetValue(...)
}
}
}
}
}
dstVar is my destination instance. pDest is the current PropertyInfo in the destination instance.
source is my source instance. pSource is the current PropertyInfo in the source instance.
The type used for my destination property has an explicit conversion from the source property type, this works without needing any
Upvotes: 4
Reputation: 11
Thanks all above for a great start on what I needed. I borrowed and also added. In my situation, I needed all of the above and also needed to search all ancestor base types for both the source and destination type to see if any of them contained an implicit or explicit conversion to my destination types. Adding this extra requirement I produced the below.
private static bool TryCast(object source, Type destType, out object result)
{
Type srcType = source.GetType();
if (srcType == destType)
{
result = source;
return true;
}
MethodInfo cast = null;
while (cast == null && srcType != typeof(object))
{
cast = GetCastMethod(srcType, srcType, destType);
if (cast == null) cast = GetCastMethod(destType, srcType, destType);
srcType = srcType.BaseType;
}
if (cast != null)
{
result = cast.Invoke(null, new object[] { source });
return true;
}
if (destType.IsEnum)
{
result = Enum.ToObject(destType, source);
return true;
}
result = null;
return false;
}
private static MethodInfo GetCastMethod(Type typeWithMethod, Type srcType, Type destType)
{
while (typeWithMethod != typeof(object))
{
foreach (MethodInfo method in typeWithMethod.GetMethods(BindingFlags.Static | BindingFlags.Public))
{
if (method.ReturnType == destType && (method.Name == "op_Explicit" || method.Name == "op_Implicit"))
{
ParameterInfo[] parms = method.GetParameters();
if (parms != null && parms.Length == 1 && parms[0].ParameterType == srcType)
return method;
}
}
typeWithMethod = typeWithMethod.BaseType;
}
return null;
}
Upvotes: 1
Reputation: 3008
I needed functionality like Ted H, but I implemented it like this:
var cast = typeof(dest).GetMethod("op_Explicit", new Type[] { typeof(source) });
var result = cast.Invoke(null, new object[] {value});
Edit: I needed a more evolved version recently, and this is what I came up with. Be aware it does not cover all available conversions.
private static object DynamicCast(object source, Type destType) {
Type srcType = source.GetType();
if (srcType == destType) return source;
var paramTypes = new Type[] { srcType };
MethodInfo cast = destType.GetMethod("op_Implicit", paramTypes);
if (cast == null) {
cast = destType.GetMethod("op_Explicit", paramTypes);
}
if (cast != null) return cast.Invoke(null, new object[] { source });
if (destType.IsEnum) return Enum.ToObject(destType, source);
throw new InvalidCastException();
}
Upvotes: 6
Reputation: 15285
Building on Herman's answer... I realized that both the source and destination class may define the conversion operator. So here is my version:
private static bool DynamicCast(object source, Type destType, out object result)
{
Type srcType = source.GetType();
if (srcType == destType) { result = source; return true; }
result = null;
BindingFlags bf = BindingFlags.Static | BindingFlags.Public;
MethodInfo castOperator = destType.GetMethods(bf)
.Union(srcType.GetMethods(bf))
.Where(mi => mi.Name == "op_Explicit" || mi.Name == "op_Implicit")
.Where(mi =>
{
var pars = mi.GetParameters();
return pars.Length == 1 && pars[0].ParameterType == srcType;
})
.Where(mi => mi.ReturnType == destType)
.FirstOrDefault();
if (castOperator != null) result = castOperator.Invoke(null, new object[] { source });
else return false;
return true;
}
Typical usage:
object a = new A();
object o;
if (DynamicCast(a, typeof(B), out o))
{
B b = (B)o;
...
}
Note the following:
out
variable (similar to TryParse methods)Upvotes: 2
Reputation: 136259
Well its really no different from your non-reflection code, you still need to explicitly cast the number to a Foo
:
propertyInfo.SetValue(testFoo,(Foo)17.17m, null);
Live example: http://rextester.com/rundotnet?code=BPQ74480
Out of interest I tried a few alternatives.
implicit
cast in Foo
- doesnt work, same error LiveConvert.ChangeType(17.17m,typeof(Foo))
- also doesnt work. LiveUpvotes: 6