This is similar to the answers posted here, but uses expression trees in order to emit il to carry between types. Expression.Convert
does the trick. The compiled delegate (caster) is cached by the internal static class. Since the original object can be inferred from the argument, I assume that it offers a cleaner call. E.g. general context:
static int Generic<T>(T t) { int variable = -1;
Grade:
You can replace caster
functionality caster
other implementations. I will compare the performance of several:
direct object casting, ie, (T)(object)S caster1 = (Func<T, T>)(x => x) as Func<S, T>; caster2 = Delegate.CreateDelegate(typeof(Func<S, T>), ((Func<T, T>)(x => x)).Method) as Func<S, T>; caster3 = my implementation above caster4 = EmitConverter(); static Func<S, T> EmitConverter() { var method = new DynamicMethod(string.Empty, typeof(T), new[] { typeof(S) }); var il = method.GetILGenerator(); il.Emit(OpCodes.Ldarg_0); if (typeof(S) != typeof(T)) { il.Emit(OpCodes.Conv_R8); } il.Emit(OpCodes.Ret); return (Func<S, T>)method.CreateDelegate(typeof(Func<S, T>)); }
Box Insert :
int
to int
casting objects β 42 ms
caster1 β 102 ms
caster2 β 102 ms
caster3 β 90 ms
caster4 β 101 ms
int
to int?
casting objects β 651 ms
caster1 β fail
caster2 β fail
caster3 β 109 ms
caster4 β fail
int?
to int
casting objects β 1957 ms
caster1 β fail
caster2 β fail
caster3 β 124 ms
caster4 β fail
enum
to int
casting objects β 405 ms
caster1 β fail
caster2 β 102 ms
caster3 β 78 ms
caster4 β fail
int
to enum
casting objects β 370 ms
caster1 β fail
caster2 β 93 ms
caster3 β 87 ms
caster4 β fail
int?
to enum
casting objects β 2340 ms
caster1 β fail
caster2 β fail
caster3 β 258 ms
caster4 β fail
enum?
to int
casting objects β 2776 ms
caster1 β fail
caster2 β fail
caster3 β 131 ms
caster4 β fail
Expression.Convert
puts a direct conversion from the source type to the target type, so it can produce explicit and implicit casts (not to mention references). Thus, this makes it possible to handle casting, which is otherwise possible only when it is not boxed (i.e., in the general method, if you execute (TTarget)(object)(TSource)
, it will explode if it is not identity conversion (as in the previous section) or link translation (as shown in the next section)). Therefore, I will include them in the tests.
Unblocked roles:
int
to double
casting objects β fail
caster1 β fail
caster2 β fail
caster3 β 109 ms
caster4 β 118 ms
enum
to int?
casting objects β fail
caster1 β fail
caster2 β fail
caster3 β 93 ms
caster4 β fail
int
to enum?
casting objects β fail
caster1 β fail
caster2 β fail
caster3 β 93 ms
caster4 β fail
enum?
to int?
casting objects β fail
caster1 β fail
caster2 β fail
caster3 β 121 ms
caster4 β fail
int?
to enum?
casting objects β fail
caster1 β fail
caster2 β fail
caster3 β 120 ms
caster4 β fail
For fun, I tested several reference conversion types:
Tested as follows:
static void TestMethod<T>(T t) { CastTo<int>.From(t); //computes delegate once and stored in a static variable int value = 0; var watch = Stopwatch.StartNew(); for (int i = 0; i < 10000000; i++) { value = (int)(object)t; // similarly value = CastTo<int>.From(t); // etc } watch.Stop(); Console.WriteLine(watch.Elapsed.TotalMilliseconds); }
Note:
My assessment is that if you don't run it at least a hundred thousand times, it's not worth it and you have almost nothing to worry about boxing. Keep in mind that delegate caching has a hit in mind. But beyond this limit, speed improves, especially when it comes to casting using nullables .
But the real advantage of the CastTo<T>
class is that it allows you to do fieldless throws, for example (int)double
in the general context. As such (int)(object)double
not executed in these scripts.
I used Expression.ConvertChecked
instead of Expression.Convert
so that arithmetic overflows and threads are checked (i.e. lead to an exception). Since il is generated at runtime, and the checked parameters are compilation time, you cannot find out the checked context of the call code. This is what you need to decide for yourself. Choose one of them or provide overload for both (better).
If casting does not exist from TSource
to TTarget
, an exception is thrown when the delegate is compiled. If you need other behavior, such as getting the default value of TTarget
, you can check for type compatibility using reflection before compiling the delegate. You have complete control over the generated code. It will be very difficult, but you need to check the compatibility of links ( IsSubClassOf
, IsAssignableFrom
), the existence of a conversion operator (will be IsAssignableFrom
), and even for some built-in type convertibility between primitive types. Be very hacks. It is easier to catch the exception and return the default delegate based on ConstantExpression
. I just declare that you can simulate the behavior of the as keyword that does not throw. Better to stay away from it and stick to the convention.
nawfal Apr 30 '14 at 15:09 2014-04-30 15:09
source share