这类似于此处发布的答案,但使用表达式树来发出 il 在类型之间进行转换。Expression.Convert
成功了。编译的委托(caster)由内部静态类缓存。由于可以从参数中推断出源对象,我猜它提供了更清晰的调用。例如,通用上下文:
static int Generic<T>(T t)
{
int variable = -1;
// may be a type check - if(...
variable = CastTo<int>.From(t);
return variable;
}
班上:
/// <summary>
/// Class to cast to type <see cref="T"/>
/// </summary>
/// <typeparam name="T">Target type</typeparam>
public static class CastTo<T>
{
/// <summary>
/// Casts <see cref="S"/> to <see cref="T"/>.
/// This does not cause boxing for value types.
/// Useful in generic methods.
/// </summary>
/// <typeparam name="S">Source type to cast from. Usually a generic type.</typeparam>
public static T From<S>(S s)
{
return Cache<S>.caster(s);
}
private static class Cache<S>
{
public static readonly Func<S, T> caster = Get();
private static Func<S, T> Get()
{
var p = Expression.Parameter(typeof(S));
var c = Expression.ConvertChecked(p, typeof(T));
return Expression.Lambda<Func<S, T>>(c, p).Compile();
}
}
}
您可以将caster
func 替换为其他实现。我将比较一些性能:
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>));
}
盒装铸件:
int
到int
对象投射 -> 42 ms
caster1 -> 102 ms
caster2 -> 102 ms
caster3 -> 90 ms
caster4 -> 101 ms
int
到int?
对象投射 -> 651 ms
caster1 -> 失败
caster2 -> 失败
caster3 -> 109 ms
caster4 -> 失败
int?
到int
对象投射 -> 1957 ms
caster1 -> 失败
caster2 -> 失败
caster3 -> 124 ms
caster4 -> 失败
enum
到int
对象投射 -> 405 ms
caster1 -> 失败
caster2 -> 102 ms
caster3 -> 78 ms
caster4 -> 失败
int
到enum
对象投射 -> 370 ms
caster1 -> 失败
caster2 -> 93 ms
caster3 -> 87 ms
caster4 -> 失败
int?
到enum
对象投射 -> 2340 ms
caster1 -> 失败
caster2 -> 失败
caster3 -> 258 ms
caster4 -> 失败
enum?
到int
对象投射 -> 2776 ms
caster1 -> 失败
caster2 -> 失败
caster3 -> 131 ms
caster4 -> 失败
Expression.Convert
将源类型直接转换为目标类型,因此它可以计算出显式和隐式转换(更不用说引用转换了)。因此,这为处理强制转换让路,否则只有在非装箱时才有可能(即,在通用方法中,如果(TTarget)(object)(TSource)
不是身份转换(如上一节)或引用转换(如后一节所示),它将爆炸))。所以我会将它们包括在测试中。
非盒装铸件:
int
到double
对象投射 ->
caster1 失败
-> caster2 失败 -> caster3 失败
-> 109 ms
caster4 -> 118 ms
enum
到int?
对象转换 ->
caster1 失败-> caster2 失败 ->
caster3 失败
-> 93 ms
caster4 -> 失败
int
到enum?
对象转换 ->
caster1 失败-> caster2 失败 ->
caster3 失败
-> 93 ms
caster4 -> 失败
enum?
到int?
对象转换 ->
caster1 失败-> caster2 失败 ->
caster3 失败
-> 121 ms
caster4 -> 失败
int?
到enum?
对象转换 ->
caster1 失败-> caster2 失败 ->
caster3 失败
-> 120 ms
caster4 -> 失败
为了好玩,我测试了一些引用类型转换:
PrintStringProperty
到string
(表示改变)
对象转换 -> 失败(很明显,因为它没有转换回原始类型)
caster1 -> 失败
caster2 -> 失败
caster3 -> 315 ms
caster4 -> 失败
string
to object
(表示保留参考转换)
对象投射 -> 78 ms
caster1 -> 失败
caster2 -> 失败
caster3 -> 322 ms
caster4 -> 失败
像这样测试:
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);
}
笔记:
我的估计是,除非你至少跑十万次,否则不值得,而且你几乎不用担心拳击。请注意,缓存代表会影响内存。但是超出这个限制,速度的提高是显着的,尤其是在涉及到 nullables 的转换时。
但是CastTo<T>
该类的真正优势在于它允许进行非装箱的强制转换,例如(int)double
在通用上下文中。因此(int)(object)double
在这些情况下失败。
我使用Expression.ConvertChecked
了Expression.Convert
not 来检查算术上溢和下溢(即导致异常)。由于 il 是在运行时生成的,并且检查的设置是编译时的事情,因此您无法知道调用代码的检查上下文。这是你必须自己决定的事情。选择一个,或为两者提供过载(更好)。
TSource
如果从to不存在强制转换TTarget
,则在编译委托时会引发异常。如果您想要不同的行为,例如获取默认值TTarget
,您可以在编译委托之前使用反射检查类型兼容性。您可以完全控制正在生成的代码。不过,这将非常棘手,您必须检查引用兼容性(IsSubClassOf
, IsAssignableFrom
)、转换运算符的存在(将是 hacky),甚至是原始类型之间的某些内置类型可转换性。会非常hacky。更容易的是捕获异常并基于ConstantExpression
. 只是说明您可以模仿as
不抛出的关键字的行为的可能性。最好远离它并坚持惯例。