这与此处发布的答案类似,但是使用表达式树发出il以在类型之间进行转换。Expression.Convert
绝招。编译的委托(广播程序)由内部静态类缓存。由于可以从参数推断出源对象,因此我猜它可以提供更清晰的调用。例如,通用上下文:
static int Generic<T>(T t)
{
int variable = -1;
variable = CastTo<int>.From(t);
return variable;
}
班级:
public static class CastTo<T>
{
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毫秒
caster1-> 102毫秒
caster2-> 102毫秒
caster3-> 90毫秒
caster4-> 101毫秒
int
至 int?
对象投射-> 651毫秒
Caster1->失败
Caster2->失败
Caster3-> 109毫秒
Caster4->失败
int?
至 int
对象投射-> 1957 ms
caster1->失败
caster2->失败
caster3-> 124 ms
caster4->失败
enum
至 int
对象投射-> 405毫秒
caster1->失败的
caster2-> 102毫秒
caster3-> 78毫秒
caster4->失败
int
至 enum
对象投射-> 370毫秒
caster1->失败
投射器2-> 93毫秒
caster3-> 87毫秒
caster4->失败
int?
至 enum
对象投射-> 2340毫秒
Caster1->失败
Caster2->失败
Caster3-> 258毫秒
Caster4->失败
enum?
至 int
对象投射-> 2776毫秒
Caster1->失败
Caster2->失败
Caster3-> 131毫秒
Caster4->失败
Expression.Convert
将直接类型转换从源类型转换为目标类型,因此它可以计算出显式和隐式转换(更不用说参考类型转换)。因此,这为处理转换提供了方法,否则只有在未装箱的情况下才可能进行转换(即,如果您使用(TTarget)(object)(TSource)
未进行身份转换(如上一节中所述)或引用转换(如下一节中所示),它将爆炸) ))。因此,我将它们包含在测试中。
非盒装演员表:
int
至 double
对象施法->失败
施法者1->失败
施法者2->失败
施法者3-> 109毫秒
施法者4-> 118毫秒
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
到object
(表示形式保留参考转换)
对象投射-> 78毫秒
Caster1->失败
Caster2->失败
Caster3-> 322毫秒
Caster4->失败
像这样测试:
static void TestMethod<T>(T t)
{
CastTo<int>.From(t);
int value = 0;
var watch = Stopwatch.StartNew();
for (int i = 0; i < 10000000; i++)
{
value = (int)(object)t;
}
watch.Stop();
Console.WriteLine(watch.Elapsed.TotalMilliseconds);
}
注意:
我的估计是,除非您至少运行十万次,否则这样做是不值得的,并且您几乎不必担心拳击。请注意,您缓存代表对内存的影响很大。但是超出这个限制,速度的提高是非常重要的,特别是涉及可空值的转换时。
但是,CastTo<T>
该类的真正优势在于,它允许进行非盒装转换,例如(int)double
在通用上下文中。因此(int)(object)double
在这些情况下失败。
我用Expression.ConvertChecked
代替Expression.Convert
以便检查算术上溢和下溢(即,异常结果)。由于il是在运行时生成的,并且检查的设置是编译时的事情,因此您无法知道调用代码的检查上下文。这是您必须自己决定的事情。选择一个,或为两者提供过载(更好)。
如果从TSource
to不存在强制转换TTarget
,则在编译委托时会引发异常。如果您想要其他行为,例如获得默认值TTarget
,则可以在编译委托之前使用反射检查类型兼容性。您可以完全控制所生成的代码。它的将是非常棘手,虽然,你必须检查参考兼容性(IsSubClassOf
,IsAssignableFrom
),转换运营商的存在(将是哈克),甚至对于一些建在原始类型之间的类型可兑换。会变得非常hacky。更容易捕获异常并基于返回默认值委托ConstantExpression
。仅说明您可以模仿as
不抛出关键字的行为的可能性。最好远离它并坚持惯例。