问题
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?
回答1:
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.
- Make it an
implicit
cast inFoo
- doesnt work, same error Live - Use
Convert.ChangeType(17.17m,typeof(Foo))
- also doesnt work. Live
回答2:
I looked at this question today while trying to copy fields by name between to objects. I was quite disappointed to see the selected answer was "you can only explicitly call an explicit operator." After all, anything else 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
回答3:
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();
}
回答4:
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:
- If the conversion is defined in both source and destination, the destination conversion operator method is given precedence
- The function returns a bool indicating success/failure, and the actual converted value in an
out
variable (similar to TryParse methods)
回答5:
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;
}
来源:https://stackoverflow.com/questions/7351289/how-do-i-perform-explicit-operation-casting-from-reflection