Как я могу использовать отражение для преобразования из десятичного числа в int? - PullRequest
11 голосов
/ 28 октября 2010

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

        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 может конвертировать только в менее строгие числовые типы. ( конечно , отсюда и «неявное» в названии). Другими словами, первое решение работало на int & rarr; decimal? но не decimal? & rarr; 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);


    }

Ответы [ 4 ]

5 голосов
/ 28 октября 2010

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

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

5 голосов
/ 28 октября 2010

Вы должны будете выполнить преобразование самостоятельно, так как компилятор обрабатывает приведение в среде без отражения.Поскольку код отражения в основном оценивает типы и объекты, как это делает компилятор, вам придется искать метод с именем 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 голосов
/ 28 октября 2010

Зачем неявное преобразование типов это хорошо работает в другом месте не с отражение?

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

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

1 голос
/ 28 октября 2010

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

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