强制类型设置器以接受兼容类型

时间:2018-03-30 23:17:59

标签: c#

说有一个班级

public class MyClass { public decimal Id { get; set; } }

我需要使用 Id 属性的setter方法为Id动态分配小数,整数,字节等,如下所示:

var setterMethod = typeof(MyClass).GetMethods(...)...First();
setterMethod.Invoke(myClassInstance, (int)1);

但由于类型不匹配(int与十进制),这不起作用。

同时这很有效:

decimal decZero = 0;
int intZero = 0;
byte byteZero = 0;
var sample1 = new MyClass{ Id = decZero };  
var sample2 = new MyClass{ Id = intZero };  
var sample3 = new MyClass{ Id = byteZero };  

这意味着C#可以隐式地转换数字类型。

如何使用类型设置器动态地将整数,小数,字节等分配给 Id 道具?

3 个答案:

答案 0 :(得分:4)

(重写了处理数字类型之间转换的答案,以及它们的可空和/或可枚举的变体)

要处理内置数字类型之间的转换,Convert.ChangeType(object,Type)方法将成为您的朋友。只需确保该值实现IConvertible接口(原始的.NET类型,如整数或双打一般)。

在可枚举和数字类型之间进行转换时,应使用Enum.ToObject(Type,object)。给定的值应该与可枚举的基础类型匹配,因此要将十进制转换为基于整数的int,需要额外的转换。

如果需要字符串解析,那么处理可枚举值将需要Enum.Parse(Type,string)。对于普通数字类型,Convert.ChangeType应该足够了,只要你不尝试从小数点格式的字符串中解析整数类型。

最后,Convert.ChangeType不能使用可空类型,因此需要首先提取基础类型。 Nullable.GetUnderlyingType(Type)就是为了这个。

总而言之,我们可以构建一个扩展的ChangeType方法:

public static object ExtendedChangeType(object value, Type targetType)
{
    if (value == null)
        return null;

    targetType = Nullable.GetUnderlyingType(targetType) ?? targetType;
    if (targetType.IsEnum)
    {
        if (value is string)
        {
            return Enum.Parse(targetType, value as string);
        }
        else
        {
            value = Convert.ChangeType(value, Enum.GetUnderlyingType(targetType));
            return Enum.ToObject(targetType, value);
        }
    }
    else
    {
        return Convert.ChangeType(value, targetType);
    }
}

然后我们可以像这样使用它:

PropertyInfo property = typeof(MyClass).GetProperty(nameof(MyClass.Id));
var value = ExtendedChangeType((int)1, valueType);
property.SetValue(myClassInstance, value);

答案 1 :(得分:1)

  

如何使用类型设置器

动态地将整数,小数,字节等分配给Id道具

你可以试试这个:

var converted = Convert.ChangeType((int)10, property.PropertyType);
property.SetValue(sample2, converted);

这里是as a fiddle,它将整数,小数和字节动态分配给十进制属性。

using System;

public class Program
{
    public static void Main()
    {
        var sample1 = new MyClass{Id = (decimal)0};
        var sample2 = new MyClass{Id = (int)0};
        var sample3 = new MyClass{Id = (byte)0};

        var property = typeof (MyClass).GetProperty(nameof(MyClass.Id));

        property.SetValue(sample1, Convert.ChangeType((decimal)10, property.PropertyType));
        property.SetValue(sample2, Convert.ChangeType((int)10, property.PropertyType));
        property.SetValue(sample3, Convert.ChangeType((byte)10, property.PropertyType));
    }
}

public class MyClass
{
    public decimal Id { get; set; }
}

答案 2 :(得分:0)

你根本不能因为covariance and contravariance而在你的情况下是不变的,但它是相关的。

虽然operator =隐式转换为所需类型,但MethodBase.Invoke希望有不同的内容。

调试代码,在方法的调用中放置一个断点。看到方法本身有以下定义 - {Void set_Id(System.Decimal)}这显然是对的吗?它需要一个十进制类型的参数,而编译器可以为你做魔术,允许在运行时期间隐式调用它。

IL指令显示使用赋值运算符

执行此操作时会发生什么
    using System;
    using System.Collections.Generic;

    namespace ConsoleApp1
    {
        class Program
        {
            static void Main(string[] args)
            {
                var testIntance = new Test();
                testIntance.Id = (int)5;
            }
        }

        public class Test { public decimal Id { get; set; } }
    }
    //
    // IL of Main method
    //
    .method private hidebysig static 
        void Main (
            string[] args
        ) cil managed 
    {
        // Method begins at RVA 0x2050
        // Code size 17 (0x11)
        .maxstack 8

        IL_0000: newobj instance void ConsoleApp1.Test::.ctor()
        IL_0005: ldc.i4.5
        IL_0006: newobj instance void [mscorlib]System.Decimal::.ctor(int32) - not so implicit for the compiler 
        IL_000b: callvirt instance void ConsoleApp1.Test::set_Id(valuetype [mscorlib]System.Decimal)
        IL_0010: ret
    } // e

让我们创造更有趣的案例

    namespace ConsoleApp1
    {
        class Program
        {
            static void Main(string[] args)
            {
                var a = 5;
                Test(a);
            }

            static void Test(decimal number) { }
        }
    }

    //
    // IL of the main method
    //
    .method private hidebysig static 
        void Main (
            string[] args
        ) cil managed 
    {
        // Method begins at RVA 0x2050
        // Code size 12 (0xc)
        .maxstack 8

        IL_0000: ldc.i4.5
        IL_0001: call valuetype [mscorlib]System.Decimal [mscorlib]System.Decimal::op_Implicit(int32)
        IL_0006: call void ConsoleApp1.Program::Test(valuetype [mscorlib]System.Decimal)
        IL_000b: ret
    } // end of method Program::Main

Invoke期望类型兼容,从某种意义上说,可以“代替另一个”(检查协方差和逆变的东西)。

有了这个说,让我们看看如果我把下面的代码会发生什么。

public class Test { public IEnumerable<int> Id { get; set; } }

...

var setter = typeof(Test).GetMethods()[1];
setter.Invoke(new Test(), new object[] { new List<int> { 1 } });

现在它通过了,因为显然List IEnumerable,并且在运行时没有违规。

现在你知道它是如何工作的,也许你可以弄清楚哪种情况最适合你。