问题 如何使用反射从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 只能转换为限制较少的数字类型。 (当然因此,名称中的“隐含”)。换句话说,第一个解决方案起作用 int → decimal? 但不是 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);


    }

1173
2017-10-28 00:06


起源



答案:


您必须自己进行转换,因为编译器在非反射环境中处理转换。由于反射代码基本上是像编译器一样评估类型和对象,因此您必须查找名为的方法 op_implicit 使用所需的参数(在您的情况下 Int32)在你的对象上并调用它。之后,您可以调用属性访问器。可能的方法如下:

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

5
2017-10-28 00:15



这基本上对我有用。至少它让我走上正轨。谢谢! - mpontillo
MethodInfo method = source.GetType().GetMethod("op_Implicit"); 比显式循环搜索更容易阅读。 - Merlyn Morgan-Graham
@Merlyn,是的,你会注意到,如果你读了我的第一个编辑,那就是我所做的,除了我还将类型添加到GetMethod()调用中。否则如果有多个重载的op_Implicit方法,我想我还是要循环? - mpontillo
@Mike:糟糕 - 你是对的。我想你的另一个选择就是使用LINQ,如果你愿意的话。 - Merlyn Morgan-Graham


答案:


您必须自己进行转换,因为编译器在非反射环境中处理转换。由于反射代码基本上是像编译器一样评估类型和对象,因此您必须查找名为的方法 op_implicit 使用所需的参数(在您的情况下 Int32)在你的对象上并调用它。之后,您可以调用属性访问器。可能的方法如下:

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

5
2017-10-28 00:15



这基本上对我有用。至少它让我走上正轨。谢谢! - mpontillo
MethodInfo method = source.GetType().GetMethod("op_Implicit"); 比显式循环搜索更容易阅读。 - Merlyn Morgan-Graham
@Merlyn,是的,你会注意到,如果你读了我的第一个编辑,那就是我所做的,除了我还将类型添加到GetMethod()调用中。否则如果有多个重载的op_Implicit方法,我想我还是要循环? - mpontillo
@Mike:糟糕 - 你是对的。我想你的另一个选择就是使用LINQ,如果你愿意的话。 - Merlyn Morgan-Graham


运行时不知道隐式转换。

你可以打电话 op_Implicit 或者通过反射的另一种转换方法,但是这样你只会获得你实现的特定转换语义。如果你使用的是C#4.0,我建议在这里使用“动态”类型,因为它会自动实现C#转换语义。


5
2017-10-28 00:18



C#4.0为+1 dynamic 建议 - Merlyn Morgan-Graham
谢谢你。我们正在使用C#3.5,但我一直看到这样的C#4.0功能看起来很方便。 - mpontillo


为什么会进行隐式类型转换   在其他地方工作得很好失败了   反射?

因为那里没有转换。隐式转换并不意味着它会自动发生,当您在代码中使用它时,编译器会为其添加代码。

如果你想在反射中使用它,你必须做同样的事情,即找到从一种类型转换到另一种类型的静态方法,然后调用它。


2
2017-10-28 00:15



感谢您的解释。我想我希望反射会有更简单的方法来做到这一点,但唉。 - mpontillo


其他答案已经涵盖 为什么 隐式转换不起作用。如果您需要隐式转换,请使用其他答案之一。如果您实际上不需要将转换隐式,则以下是一个更简单的选项:

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) cast,你得到这个错误:

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

1
2017-10-28 01:00



新颖的想法;它让我至少轻笑。我想我应该在这个问题上说过这个,但真正的目标是让这个充满活力;我并不总是这样做(十进制?)(int)。 =) - mpontillo
@Mike:我想的很多,但你不能通过互联网测量一个问题的经验。对于你必须施展的新手来说,这不是100%明显的 两次 让这个工作。我会留下这个答案,以防对不熟练的人有用,但仍会遇到同样的错误。 - Merlyn Morgan-Graham