有奖征答(1):逆泛型执行器

首页

2014-05-15

cover

微信公众账号上次给我的感觉很不好,这个编辑器又弱又丑又难用,发出去的消息甚至连我自己都没有收到。于是我打算还是不在这里发布完整的文章了,最多发点简介,感想或是八卦等等,稍微认真点的文章还是会发在博客上。假如您还没看过上次那篇文章,可以点击下方“阅读全文”来查看经过修正及美化的版本。

不过有了微信公众账号,倒也可以来玩点特别的事情。似乎有奖征答还是挺有趣的,先试一期罢。本期的奖品是数码配件套装一份(见上方图片),包含清洁布,清洁刷,清洁剂,鼠标,以及万用插座适配器一份,妈妈再也不用担心您的港版机器插不上国内插座了。

本次的主题是“逆泛型执行器”。什么叫做“逆泛型”呢?其实我也不太确定,这只是我瞎取的一个名字。我们知道,泛型的作用是将“同一段代码”面向“不同实际类型”来执行,但这次我们要反其道而行之。

且看下方的接口:

public interface ICaller {
    TResult Call<T, TResult>(T arg);
}

这个接口本身并没有泛型参数,但它包含一个泛型的Call方法,两个具体的泛型参数便是方法的输入和输出。那么我们想要为它提供什么样的实现呢,具体示例可以看下方代码:

public class TicksToDateTimeCaller : ICaller {
    public TResult Call<T, TResult>(T arg) {
        Debug.Assert(typeof(T) == typeof(long) && typeof(TResult) == typeof(DateTime));

        return (TResult)(object)TicksToDateTime((long)(object)arg);
    }

    private DateTime TicksToDateTime(long ticks) {
        return new DateTime(ticks);
    }
}

如图所示,事实上我们只支持一种泛型参数组合,这是一种从“通用”到“不通用”的转变,于是我将其称之为“逆泛型”。我们在Call方法中判断两个泛型参数的类型,假如它是我们所期望的longDateTime,我们将会调用那段具体的逻辑。

这段代码当然可以正常工作,但是可能您已经注意到了,这里的类型转化实在令人烦恼。因为我们没法将一个泛型的T直接转化为long,于是我们只能将其先转化成object类型。在实际运行时,这个object则会安全地转化回long,自然不会出错。将DateTime转为TResult也会遇到类似的问题。

但这里所产生的装箱和拆箱都是额外的开销,频繁调用将会对性能产生比较严重的负面影响。那么这次的问题是,怎样编写一个高性能的实现呢?所谓高性能,无非是速度快,开销低,例如,假如可以避免这里的拆箱装箱,这性能自然会更高一些了。

这里还有两个问题需要说明。首先,这段代码看似蛋疼,但实际上是有其重要价值的,目前暂且不关心这方面问题。其次,上方的TicksToDateTimeCaller实现事实上并不会产生任何装箱,不信您可以尝试在Release模式下编译执行(也记得不要带调试器)。CLR会为不同的值类型组合生成不同的原生代码,因此在longDateTime组合中,它可以清楚地意识到这段代码的意图,避免无谓的类型转化。

换句话说,请在Debug模式中编译执行这段代码。我理想中的解决方案,会用到一些平时不太容易被人注意,但在实践中会非常有用的技巧,因此还是值得一试的。

请把实现同样贴在GitHub上,并将链接通过微信消息发送给我(不接受其提交他方式)。我将选出回答最好的那位赠与奖品,评价标准以正确性为主,易用性为辅,其他方面次之。如对评价标准有所争议,本人保留最终解释权——够酷吧?

到时候我自然也会发布我心目中最合适做法,并对收到的解答进行点评。

阅读原文