说有一个班级
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 道具?
答案 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,并且在运行时没有违规。
现在你知道它是如何工作的,也许你可以弄清楚哪种情况最适合你。