Как выполнить явное приведение операции из отражения? - PullRequest
11 голосов
/ 08 сентября 2011

Я хочу использовать отражение и сделать неявное или явное покрытие с использованием отражения.

Учитывая, что я определил Foo таким образом

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();
    }
}

Когда я запускаю этот код

decimal someNumber = 42.42m;

var test = (Foo)someNumber;

Console.WriteLine(test);        // Writes 42.42 No problems

Когда я пытаюсь определить класс с Foo в качестве типа члена и использую отражение, чтобы установить его.Я получаю следующее исключение.

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)

Вот код, который я использую для установки свойства с отражением

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

Как я могу выполнить это преобразование?

Ответы [ 5 ]

5 голосов
/ 08 сентября 2011

Ну, это на самом деле ничем не отличается от вашего кода без отражения, вам все равно нужно явно привести число к Foo:

propertyInfo.SetValue(testFoo,(Foo)17.17m, null);

Живой пример: http://rextester.com/rundotnet?code=BPQ74480

Из интереса я попробовал несколько альтернатив.

  1. Сделать implicit приведение в Foo - не работает, та же ошибка Live
  2. Использовать Convert.ChangeType(17.17m,typeof(Foo)) - тоже не работает. Live
4 голосов
/ 17 июля 2013

Мне нужна была такая функциональность, как Ted H, но я реализовал ее так:

var cast = typeof(dest).GetMethod("op_Explicit", new Type[] { typeof(source) });
var result = cast.Invoke(null, new object[] {value});

Редактировать: Недавно мне понадобилась более развитая версия, и это то, что я придумал. Имейте в виду, что он не охватывает все доступные конверсии.

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 голосов
/ 07 сентября 2012

Я посмотрел на этот вопрос сегодня, пытаясь скопировать поля по имени между объектами. Я был очень разочарован, увидев, что выбранный ответ был «вы можете только явно вызвать явный оператор». В конце концов, все остальное можно сделать с помощью отражения.

Моей проблемой был метод отражения, пытающийся сделать глубокую копию между двумя классами из-за сложного типа. Я попытался определить explicit operator преобразование, но, похоже, оно не вызывалось, поэтому я нашел способ получить его по рефлексии. Используя некоторые другие исследования о вызове статических методов, я обнаружил, что это работает для меня при копировании сложного типа, хранящегося в pSource, в другой тип в свойстве pDest. тип в pDest имеет преобразование из типа pSource.


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 - мой экземпляр назначения. pDest является текущим PropertyInfo в целевом экземпляре.

источник - мой исходный экземпляр. pSource является текущим PropertyInfo в исходном экземпляре.

Тип, используемый для моего целевого свойства, имеет явное преобразование из исходного типа свойства, это работает без необходимости

1 голос
/ 19 января 2015

Опираясь на Ответ Германа ... Я понял, что и источник, и класс назначения могут определять оператор преобразования. Итак, вот моя версия:

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;
}

Типичное использование:

object a = new A();
object o;
if (DynamicCast(a, typeof(B), out o))
{
    B b = (B)o;
    ...
}

Обратите внимание на следующее:

  • Если преобразование определено как в источнике, так и в месте назначения, метод оператора назначения получателя имеет приоритет
  • Функция возвращает логическое значение, указывающее успех / неудачу, и фактическое преобразованное значение в переменной out (аналогично методам TryParse)
0 голосов
/ 29 августа 2017

Спасибо всем выше за отличный старт на то, что мне было нужно. Я позаимствовал и тоже добавил. В моей ситуации мне потребовалось все вышеперечисленное, а также нужно было выполнить поиск во всех базовых типах предков как исходного, так и целевого типа, чтобы увидеть, содержит ли какой-либо из них неявное или явное преобразование в мои целевые типы. Добавляя это дополнительное требование, я произвел следующее.

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