动态生成的IL中的值类型转换 [英] Value Type Conversion in Dynamically Generated IL

查看:43
本文介绍了动态生成的IL中的值类型转换的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述


更新

一年后,我终于意识到了这种现象的原因。
本质上,不能将一个对象取消装箱而不同于将其包装成
的类型(即使该类型强制转换或转换为目标
类型),如果您不这样做,知道您必须以某种方式发现它
的正确类型。赋值可能是完全有效的,但是
不能自动执行。

Update
Over a year later, and I finally realized the cause of this behavior. Essentially, an object can't be unboxed to a different type than it was boxed as (even if that type casts or converts to the destination type), and if you don't know the correct type you have to discover it somehow. The assignment may be perfectly valid, but it is not feasible for this to happen automatically.

例如,即使一个字节适合Int64,您也可以无法将
字节拆箱的时间太长。您必须先将一个字节拆箱为一个字节,然后将其强制转换。

For example, even though a byte fits into an Int64, you can't unbox a byte as a long. You must unbox a byte as a byte, and then cast it.

如果您没有足够的信息来这样做,则必须使用其他方式(如下所示) )。

If you don't have enough information to do that, you must use another means (as demonstrated below).

表示形式和身份

原始问题

我正在与IL合作,以提高通常通过反射处理的许多任务的性能。为此,我大量使用 DynamicMethod 类。

I'm working with IL to increase the performance of many tasks which are commonly handled with reflection. To accomplish this, I'm heavily using the DynamicMethod class.

我编写了用于设置属性的动态方法在一个对象上。这允许开发人员仅基于名称即时设置属性。

I've written dynamic methods for setting properties on an object. This allows a developer to set properties on the fly based only on name. This works great for tasks such as loading records from a database into a business object.

但是,我坚持一件事(可能很简单):转换值类型,甚至转换值类型。较大或较小的类型(例如将字节的值放入Int32中)。

However, I am stuck on one (probably simple) thing: converting value types, even larger to smaller types (such as putting the value of a byte into an Int32).

这里是我用来创建动态属性设置器的方法。请注意,我删除了除IL生成部分以外的所有内容。

Here is the method I am using to create a dynamic property setter. Note that I have removed everything but the IL generation portion.

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

我尝试在IL生成时检查属性类型并使用转换操作码。尽管如此,代码仍会引发 InvalidCastException 。此示例显示检查(我认为)应确保将堆栈上的任何值都转换为匹配为其分配属性的类型。

I have tried checking the property type at IL-generation time and using conversion OpCodes. In spite of this, the code still throws an InvalidCastException. This example shows a check that (I think) should ensure that whatever value is on the stack is converted to match the type of property to which it is being assigned.

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

在将值类型拆箱之前或之后,我也尝试过投射:

I've also tried casting before or after unboxing the value type, such as:

if( propertyType.IsValueType )
{
    // cast here?
    il.Emit( OpCodes.Unbox_Any, propertyType );
    // or here?
}

我想我可以创建IL来动态创建转换对象并调用 ChangeType(),但这在大多数情况下甚至都不是问题(当类型匹配时,有没问题)。

I guess I could create IL to dynamically create the Convert object and call ChangeType() but that seems wasteful when most of the time this isn't even an issue (when types match, there is no problem).

总结问题:当我将值类型传递给动态生成的方法时(如果它与属性类型不完全匹配)如果将其分配给它,即使目标类型的大小大于源类型的大小,也将引发InvalidCastException。我尝试过的类型转换不起作用。

如果您需要更多信息来回答问题,请告诉我。

If you need more information to answer the question, please let me know.

编辑:@ JeffN825在查看转换方面处于正确的轨道。我曾经考虑过System.Convert类,但是排除了它过于昂贵的可能性。但是,有了目标类型,您可以创建一个例程,该例程仅调用适合于该类型的方法。这(基于测试)似乎相对便宜。生成的代码如下所示:

@JeffN825 was on the right track with looking at conversion. I had considered the System.Convert class, but ruled it out as being too expensive. However, with the destination type in hand, you can create a routine that only calls the method appropriate for the type. This (based on testing) seems relatively cheap. The resulting code looks something like this:

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

被授予,这会导致一个巨大的if / else语句(当所有类型都是实施),但它与BCL的功能没有什么不同,并且仅在生成IL时执行此检查,并且每次调用时执行。因此,它将选择正确的Convert方法并编译对它的调用。

Granted, this results in a giant if/else statement (when all types are implemented) but its not dissimilar to what the BCL does, and this check is only performed when the IL is generated, and not with every call. Thus, it picks the correct Convert method and compiles a Call to it.

请注意,需要 OpCodes.Call ,而不是 OpCodes.Callvirt ,因为 Convert 对象的方法是静态的。

Note that OpCodes.Call is required, not OpCodes.Callvirt, as the Convert object's methods are static.

性能可观;临时测试显示,对动态生成的set方法进行了1,000,000次调用,耗时约40毫秒。

Performance is respectable; casual testing shows 1,000,000 calls to the dynamically generated set method taking about 40ms. Beats the heck out of reflection.

推荐答案

我知道这并不能直接回答您的问题,但是必须保持许多不同IL生成实现,我发现在使用表达式树时取得了更好的成功。

I know this doesn't directly answer your question, but after having to maintain many different IL generation implementations, I've found better success in using Expression Trees.

它们可作为.NET 2.0 / 3.5的DLR的一部分使用,或直接集成到.NET 4.0中。

They're available as part of the DLR for .NET 2.0/3.5, or integrated directly in .NET 4.0.

您可以将表达式树编译为lambda或将事件直接编译为 DynamicMethod

You can compile your expression tree to a lambda or event emit directly to a DynamicMethod.

,底层的Expression Tree API使用相同的 ILGenerator 机制生成IL。

Ultimately, the underlying Expression Tree API generates IL using the same ILGenerator mechanism.

PS当我像这样调试IL生成时,我喜欢创建一个简单的Console测试应用程序,并创建Reflector的编译代码。

对于您的问题,我尝试了以下操作:

P.S. When I'm debugging IL generation like this, I like to create a simple Console test application and Reflector the compiled code.
For your problem, I tried the following:

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

生成的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 

就像您一样,将其装箱到值类型。你猜怎么了?我收到无效的强制转换异常!因此,问题不在于您生成的IL。我建议您尝试将其用作IConvertable:

It's unboxing to the value type just like you do. Guess what? I get an invalid cast exception! So the problem isn't the IL you're generating. I'd recommend you try using it as an 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 

这篇关于动态生成的IL中的值类型转换的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆