12

更新
1年以上後、私はついにこの動作の原因に気づきました。基本的に、オブジェクトは、ボックス化されたものとは異なるタイプにボックス化解除することはできません(そのタイプが宛先タイプにキャストまたは変換された場合でも)。正しいタイプがわからない場合は、何らかの方法でオブジェクトを検出する必要があります。割り当ては完全に有効である可能性がありますが、これが自動的に行われることは現実的ではありません。

たとえば、バイトがInt64に収まる場合でも、バイトをlongとしてアンボックス化することはできません。バイトをバイトとしてボックス化解除してから、キャストする必要があります。

それを行うのに十分な情報がない場合は、別の手段を使用する必要があります(以下に示すように)。

表現とアイデンティティ

元の問題

私はILと協力して、一般的にリフレクションで処理される多くのタスクのパフォーマンスを向上させています。これを達成するために、私はDynamicMethodクラスを多用しています。

オブジェクトにプロパティを設定するための動的メソッドを作成しました。これにより、開発者は名前のみに基づいてその場でプロパティを設定できます。これは、データベースからビジネスオブジェクトへのレコードのロードなどのタスクに最適です。

しかし、私は1つの(おそらく単純な)ことに固執しています。値型をさらに大きな型から小さな型に変換することです(たとえば、バイトの値を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 );

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

また、次のような値型のボックス化を解除する前または後にキャストを試みました。

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

ConvertILを作成してオブジェクトと呼び出しを動的に作成できると思います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 );
}

確かに、これは巨大なif / elseステートメント(すべてのタイプが実装されている場合)になりますが、BCLの動作と同じであり、このチェックはILが生成されたときにのみ実行され、すべての呼び出しでは実行されません。したがって、正しいConvertメソッドを選択し、そのメソッドへの呼び出しをコンパイルします。

オブジェクトのメソッドは静的OpCodes.Callであるため、必須ではなくOpCodes.Callvirt、必須であることに注意してください。Convert

パフォーマンスは立派です。カジュアルテストでは、動的に生成されたsetメソッドへの1,000,000回の呼び出しに約40ミリ秒かかることが示されています。反射から一体を打ち負かします。

4

2 に答える 2

8

これがあなたの質問に直接答えないことは知っていますが、多くの異なるIL生成の実装を維持しなければならなかった後、式ツリーを使用することでより良い成功を収めました。

これらは、DLR for .NET 2.0 / 3.5の一部として、または.NET4.0に直接統合されて利用できます。

式ツリーをラムダにコンパイルするか、イベントを直接に出力することができますDynamicMethod

ILGenerator最終的に、基盤となる式ツリーAPIは、同じメカニズムを使用してILを生成します。

PSこのようにIL生成をデバッグするときは、単純なコンソールテストアプリケーションを作成し、コンパイルされたコードを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; }
}

そして、生成される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として使用することをお勧めします。

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 
于 2011-04-21T22:59:05.527 に答える
2

値をボックス化解除するには、最初にボックス化する必要があります。ボックス化解除がスローされないようにするには、ボックス化する前に、値をボックス化解除したタイプに変換する必要があります。

ただし、プロパティセッターのタイプは既知であり、値のタイプを処理しているため、ボックス化/ボックス化解除する必要はまったくありません。

Int32たとえば、Int64引数を指定して型のプロパティセッターを呼び出したい場合は、次のようになります。

// Int 64 argument value assumed on top of stack now
conv.i4  // convert it to int32
callvirt   ...
于 2011-04-21T23:31:37.227 に答える