更新
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?
}
Convert
ILを作成してオブジェクトと呼び出しを動的に作成できると思います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ミリ秒かかることが示されています。反射から一体を打ち負かします。