Tim*_*ora 12 c# il type-conversion
更新
一年后,我终于意识到了这种行为的原因.本质上,一个对象不能被拆箱到不同的类型(即使该类型转换或转换为目标类型),如果你不知道正确的类型,你必须以某种方式发现它.赋值可能完全有效,但这不可能自动发生.例如,即使一个字节适合Int64,也不能将一个字节拆分为long.您必须将一个字节取消装箱作为一个字节,然后再将其转换.
如果您没有足够的信息来执行此操作,则必须使用其他方法(如下所示).
原始问题
我正在与IL合作,以提高通常用反射处理的许多任务的性能.为了实现这一点,我正在大量使用这门DynamicMethod课程.
我编写了动态方法来设置对象的属性.这允许开发人员仅基于名称动态设置属性.这适用于将数据库中的记录加载到业务对象等任务.
但是,我坚持一个(可能很简单)的事情:将值类型转换为更小的类型(例如将字节的值放入Int32).
这是我用来创建动态属性设置器的方法.请注意,我删除了除IL生成部分之外的所有内容.
// An "Entity" is simply a base class for objects which use these dynamic methods.
// Thus, this dynamic method takes an Entity as an argument and an object value
DynamicMethod method = new DynamicMethod( string.Empty, null, new Type[] { typeof( Entity ), typeof( object ) } );
ILGenerator il = method.GetILGenerator();
PropertyInfo pi = entityType.GetProperty( propertyName );
MethodInfo mi = pi.GetSetMethod();
il.Emit( OpCodes.Ldarg_0 ); // push entity
il.Emit( OpCodes.Castclass, entityType ); // cast entity
il.Emit( OpCodes.Ldarg_1 ); // push value
if( propertyType.IsValueType )
{
il.Emit( OpCodes.Unbox_Any, propertyType );
// type conversion should go here?
}
else
{
il.Emit( OpCodes.Castclass, propertyType ); // cast value
}
//
// The following Callvirt works only if the source and destination types are exactly the same
il.Emit( OpCodes.Callvirt, mi ); // call the appropriate setter method
il.Emit( OpCodes.Ret );
Run Code Online (Sandbox Code Playgroud)
我已经尝试在IL生成时检查属性类型并使用转换OpCodes.尽管如此,代码仍然抛出InvalidCastException.这个例子显示了一个检查(我认为)应该确保堆栈上的任何值都被转换为匹配它所分配的属性类型.
if( pi.PropertyType == typeof( long ) )
{
il.Emit( OpCodes.Conv_I8 );
}
else if( pi.PropertyType == typeof( int ) )
{
il.Emit( OpCodes.Conv_I4 );
}
else if( pi.PropertyType == typeof( short ) )
{
il.Emit( OpCodes.Conv_I2 );
}
else if( pi.PropertyType == typeof( byte ) )
{
il.Emit( OpCodes.Conv_I1 );
}
Run Code Online (Sandbox Code Playgroud)
我也在取消装箱值类型之前或之后尝试过铸造,例如:
if( propertyType.IsValueType )
{
// cast here?
il.Emit( OpCodes.Unbox_Any, propertyType );
// or here?
}
Run Code Online (Sandbox Code Playgroud)
我想我可以创建IL来动态创建Convert对象和调用,ChangeType()但这在大多数情况下甚至不是问题(当类型匹配时,没有问题)似乎是浪费.
总结问题:当我将值类型传递给动态生成的方法时,如果它与它所分配的属性类型不完全匹配,则会抛出InvalidCastException,即使目标类型的大小更大比源类型.我试过的类型转换不起作用.
如果您需要更多信息来回答这个问题,请告诉我.
编辑:@ JeffN825正在寻找转换的正确轨道.我曾考虑过System.Convert类,但认为它过于昂贵.但是,使用目标类型,您可以创建仅调用适合该类型的方法的例程.这(基于测试)似乎相对便宜.生成的代码如下所示:
il.Emit( OpCodes.Call, GetConvertMethod( propertyType );
internal static MethodInfo GetConvertMethod( Type targetType )
{
string name;
if( targetType == typeof( bool ) )
{
name = "ToBoolean";
}
else if( targetType == typeof( byte ) )
{
name = "ToByte";
}
else if( targetType == typeof( short ) )
{
name = "ToInt16";
}
else if( targetType == typeof( int ) )
{
name = "ToInt32";
}
else if( targetType == typeof( long ) )
{
name = "ToInt64";
}
else
{
throw new NotImplementedException( string.Format( "Conversion to {0} is not implemented.", targetType.Name ) );
}
return typeof( Convert ).GetMethod( name, BindingFlags.Static | BindingFlags.Public, null, new Type[] { typeof( object ) }, null );
}
Run Code Online (Sandbox Code Playgroud)
当然,这会产生一个巨大的if/else语句(当所有类型都被实现时),但它与BCL没有什么不同,并且此检查仅在生成IL时执行,而不是在每次调用时执行.因此,它选择正确的Convert方法并编译一个Call.
请注意,这OpCodes.Call是必需的,OpCodes.Callvirt因为Convert对象的方法是静态的.
表现可敬; 临时测试显示动态生成的set方法的1,000,000次调用大约需要40ms.击败了反射.
我知道这并没有直接回答你的问题,但在必须维护许多不同的IL生成实现后,我发现使用表达式树更成功.
它们作为DLR for .NET 2.0/3.5的一部分提供,或直接集成在.NET 4.0中.
您可以将表达式树编译为lambda或事件直接发送到a DynamicMethod.
最终,底层Expression Tree API使用相同的ILGenerator机制生成IL .
PS当我调试这样的IL生成时,我喜欢创建一个简单的Console测试应用程序和Reflector编译的代码.
对于您的问题,我尝试了以下方法:
static class Program
{
static void Main(string[] args)
{
DoIt((byte) 0);
}
static void DoIt(object value)
{
Entity e = new Entity();
e.Value = (int)value;
}
}
public class Entity
{
public int Value { get; set; }
}
Run Code Online (Sandbox Code Playgroud)
并且生成的IL是:
L_0000: nop
L_0001: newobj instance void ConsoleApplication2.Entity::.ctor()
L_0006: stloc.0
L_0007: ldloc.0
L_0008: ldarg.0
L_0009: unbox.any int32
L_000e: callvirt instance void ConsoleApplication2.Entity::set_Value(int32)
L_0013: nop
L_0014: ret
Run Code Online (Sandbox Code Playgroud)
它就像你一样打开了值类型.你猜怎么着?我得到一个无效的演员异常!所以问题不在于你正在产生的IL.我建议您尝试将其用作IConvertable:
static void DoIt(object value)
{
Entity e = new Entity();
e.Value = ((IConvertible) value).ToInt32(null);
}
L_0000: nop
L_0001: newobj instance void ConsoleApplication2.Entity::.ctor()
L_0006: stloc.0
L_0007: ldloc.0
L_0008: ldarg.0
L_0009: castclass [mscorlib]System.IConvertible
L_000e: ldnull
L_000f: callvirt instance int32 [mscorlib]System.IConvertible::ToInt32(class [mscorlib]System.IFormatProvider)
L_0014: callvirt instance void ConsoleApplication2.Entity::set_Value(int32)
L_0019: nop
L_001a: ret
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
3293 次 |
| 最近记录: |