Как я могу использовать отражение для преобразования из int в десятичный?

У меня есть код (который отлично работает), который выглядит примерно так:

        int integer = 42;
        decimal? castTo = integer;

Затем я хотел сделать что-то подобное с отражением, с некоторым кодом, который выглядит так:

object value = source; // source was an int originally
var parameters = new object[1];    
    ...
    parameters[0] = value;
    var setMethod = property.GetSetMethod();     
    // Call the set method, which takes a decimal? as a parameter
    setMethod.Invoke(o, parameters);  

Когда я это делаю, я получаю:

failed: System.ArgumentException : Object of type 'System.Int32' cannot be converted to type 'System.Nullable`1[System.Decimal]'.
    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.MethodBase.Invoke(Object obj, Object[] parameters)

Почему неявное преобразование типов, которое отлично работает в другом месте, не отражается? Есть ли уловка для использования отражения для выполнения этого преобразования?


Изменить. Спасибо всем за ответы. Вот решение, которое я придумал, основываясь на ответах:

    private object Convert(object source, Type destinationType)
    {
        if (source == null)
        {
            return null;
        }

        var sourceType = source.GetType();

        // unwrap nullable types
        var nullableType = Nullable.GetUnderlyingType(destinationType);
        if(nullableType != null)
        {
            destinationType = nullableType;
        }

        nullableType = Nullable.GetUnderlyingType(sourceType);
        if(nullableType != null)
        {
            sourceType = nullableType;
        }


        var implicitCastMethod =
            destinationType.GetMethod("op_Implicit", 
                                 new[] { sourceType } );

        if(implicitCastMethod == null)
        {
            return null;
        }

        return implicitCastMethod.Invoke(null, new[] { source });
    }

Изменить # 2. Я хотел бы, чтобы кто-то упомянул System.Convert.ChangeType(), который обрабатывает эти случаи и многое другое. Оказывается, что op_Implicit может преобразовывать только менее строгие числовые типы. (конечно, следовательно, "Неявный" в названии). Другими словами, первое решение работало для intdecimal?, но не decimal?int. (Кажется, мне нужно будет изменить этот код, чтобы также попробовать op_Explicit, если неявное преобразование завершилось неудачно, если бы я хотел иметь возможность обрабатывать преобразование из decimal? обратно в int.)

Так как System.Convert.ChangeType() не работает с типами Nullable<>, я, наконец, закончил тем, что использовал код, похожий на то, что я нашел здесь ( слегка измененный):

    private static object Convert(object source, Type destinationType)
    {
        if(destinationType == null)
        {
            throw new ArgumentNullException("destinationType");
        }

        if(destinationType.IsGenericType && 
            destinationType.GetGenericTypeDefinition().Equals(typeof(Nullable<>)))
        {
            if (source == null)
            {
                return null;
            }
            destinationType = Nullable.GetUnderlyingType(destinationType);                
        }

        return System.Convert.ChangeType(source, destinationType);


    }

Ответ 1

Вам нужно будет выполнить преобразование самостоятельно, так как компилятор обрабатывает листинг в среде без отражения. Поскольку код отражения в основном оценивает типы и объекты, подобные компилятору, вам придется искать метод с именем op_implicit с необходимыми параметрами (в вашем случае Int32) на вашем объекте и вызывать его. После этого вы можете вызвать свойство accessor. Возможный путь:

//search for an implicit cast operator on the target type
MethodInfo[] methods = targetType.GetMethods();
foreach(MethodInfo method = source.GetType().GetMethod("op_Implicit"))
{
  if (method.Name == "op_Implicit")
  {
    ParameterInfo[] parameters = method.GetParameters();
    if (parameters.Length == 1 && parameters[0].ParameterType == value.GetType())
    {
      value = method.Invoke(obj,new object[]{value});
      break;
    }
  }
}

Ответ 2

Время выполнения не знает о неявных преобразованиях.

Вы можете вызвать op_Implicit или другой метод преобразования через отражение, но таким образом вы получите только определенную семантику преобразования, которую вы реализуете. Если вы используете С# 4.0, я бы рекомендовал использовать здесь "динамический" тип, поскольку он автоматически реализует семантику преобразования С#.

Ответ 3

Почему неявное преобразование типа который отлично работает в других местах, отражение?

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

Если вы хотите использовать его в отражении, вы должны сделать то же самое, то есть найти статический метод, который выполняет преобразование из одного типа в другой, и называть его.

Ответ 4

Другие ответы уже объяснили, почему неявное преобразование не работает. Если вам нужно, чтобы ваше преобразование было неявным, используйте один из других ответов. Если вам действительно не нужно, чтобы преобразование было неявным, вот более простой вариант:

class Test
{
  public decimal? Val { get; set; }
}

class Program
{
  static void Main(string[] args)
  {
    object o = new Test();
    object source = 5;
    var setMethod = typeof(Test).GetProperty("Val").GetSetMethod();
    // Just do the cast explicitly
    setMethod.Invoke(o, new object[] { (decimal?)(int)source });
  }
}

Обратите внимание, что если вам не хватает приклада (decimal?), вы получите ошибку, указанную в исходном вопросе. Если вам не хватает (int), вы получите эту ошибку:

Unhandled Exception: System.InvalidCastException: Specified cast is not valid.
   at Program.Main(String[] args) in ...\ConsoleApplication1\Program.cs:line 14