最近在写一段代码的时候,为了兼容各种未知的类型,以及完成一个根据类型自动分派的任务到对应的处理器时,使用了这样的一个契约:

    用一个object数组来转递值,每个处理器则声明一个可以处理的类型组合。

    然后在写如何分派时,发生了一段小插曲。

分派原则

    首先,明确一下分派的原则,如果处理器声明能处理的类型是:

string,int

    那么只有当对象数组的长度为2,并且类型分别为string和int时,才会分派到这个处理器上去执行,不过,要注意的一点是,string是引用类型,因此值可以为null,最终,声明为string,int的处理器可以接受下列参数:

"aaa",123

null,123

    其他形式的参数,不应该分派给该处理器。

    然后,再说说更复杂的情况,如果处理器声明能处理的类型是:

IClonable,int?

    那么,问题要稍微复杂一点,对于第一个参数而言,要求是任意实现了IClonable接口的对象(例如任何string,数组),当然null也是允许的。对于第二个参数而言,int?可以接受任意int或者null。

    综合起来,也就是下列值是允许的:

"aaa”, 123

new int[0], null

null, null

    最后,给两个特殊的契约,如果可以传递任意参数,那么就指定object,如果必须某一项为null,则类型指定null。

基础实现

    统一好上面的契约,开始实现时,发现事情没有想象中这么简单,对于值为null的情况,需要判断类型是否是引用类型(!Type.IsValueType),如果是值类型,还要额外允许可空类型(Nullable.GetUnderlyingType)。

    对于非可空的情况,则判断继承树(Type.IsAssignableFrom),在加上其他的逻辑,代码也不短,更要命的是其中用到了N多的反射,而且每次都要反射一把,性能绝对是个问题。

    (如果各位有兴趣,可以自己实现一下,这里就不写了)

思路

    解决这些问题之前,先想一下,如果这些类型在编译时已知,那么代码会怎么写哪?

    假设需要判定对象obj中间的类型是不是string,那么会写下面的代码:

obj is string

    是不是IClonable,那么会写下面的代码:

obj is IClonable

    是不是int,那么会写下面的代码:

obj is int

    是不是int?,那么会写下面的代码:

obj is int?

    看到这里发现什么了?在c#中这些判定惊人的一致,唯一需要特殊处理的是null。所以想象一下,如果能把动态的类型实例,转换成静态的类型信息,对object数组的类型判定将变得非常简单。

进阶实现

    那么运用什么手段可以将动态类型实例变成静态的类型信息哪?

    答案有很多,当然都围绕一个主题——动态代码生成

    这里用最轻量级的动态代码生成方式Emit实现的,具体过程就略过了,最终的核心代码部分如下:

        public static Func<object[], bool> CreateFastTypeChecker(this Type[] expectedTypes)
{
// check parameters ...
DynamicMethod dm = new DynamicMethod("Zhenway's Fast Type Checker", typeof(bool), new Type[] { typeof(object[]) });
var il = dm.GetILGenerator();
Label retFalse = il.DefineLabel();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldlen);
il.Emit(OpCodes.Ldc_I4, expectedTypes.Length);
il.Emit(OpCodes.Bne_Un, RetFalse);
for (int index = 0; index < expectedTypes.Length; index++)
{
bool mustBeNull = expectedTypes[index] == null;
bool mustNotBeNull = !mustBeNull && expectedTypes[index].IsValueType &&
Nullable.GetUnderlyingType(expectedTypes[index]) == null;
if (expectedTypes[index] == typeof(object))
continue;
Label Next = il.DefineLabel();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldc_I4, index);
il.Emit(OpCodes.Ldelem, typeof(object));
il.Emit(OpCodes.Ldnull);
il.Emit(OpCodes.Cgt_Un);
if (mustNotBeNull)
il.Emit(OpCodes.Brfalse, retFalse);
else
il.Emit(OpCodes.Brfalse_S, Next);
if (mustBeNull)
{
il.Emit(OpCodes.Br, RetFalse);
}
else
{
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldc_I4, index);
il.Emit(OpCodes.Ldelem, typeof(object));
il.Emit(OpCodes.Isinst, expectedTypes[index]);
il.Emit(OpCodes.Ldnull);
il.Emit(OpCodes.Cgt_Un);
il.Emit(OpCodes.Brfalse, RetFalse);
}
il.MarkLabel(Next);
}
il.Emit(OpCodes.Ldc_I4_1);
il.Emit(OpCodes.Ret);
il.MarkLabel(retFalse);
il.Emit(OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Ret);
return (Func<object[], bool>)dm.CreateDelegate(typeof(Func<object[], bool>));
}
整个过程只需要花50行左右的代码,但是却可以带来非常高的执行效率(当然,创建成本比较高,如果,只跑一两次,那就亏了)。
posted on 2010-12-17 16:15  Zhenway  阅读(440)  评论(11编辑  收藏  举报