Hello World
Spiga

逆泛型执行器

2014-05-28 23:25 by 老赵, 9454 visits

话说微信公众账号上的第一期有奖征答活动发布至今已有两周时间,不过参与人数寥寥,是太难,还是奖品不够吸引人?大家要多参与,我们才能长期互动嘛。现在我就对第一期的题目“逆泛型执行器”进行简单讲解吧,其实这题很简单,以后类似难度的题目可能会放在“快速问答”环节中。话说第一期的快速问答还在进行之中,大家加油。

参考解答

所谓“逆泛型”,即我们希望可以在一个泛型操作中,只对特定的类型组合进行响应。例如:

public class TicksToDateTimeCaller {

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

    public TResult Call<T, TResult>(T arg) {
        return (TResult)(object)TicksToDateTime((long)(object)arg);
    }
}

以上代码会产生无谓的代码转换及装箱拆箱操作(尽管它们在 Release 模式下会被优化掉),为了避免这点,我们可以这么做:

public class TicksToDateTimeCaller {

    private static class Cache<T, TResult> {
        public static Func<T, TResult> Call;
    }

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

    static TicksToDateTimeCaller() {
        Cache<long, DateTime>.Call = TicksToDateTime;
    }

    public TResult Call<T, TResult>(T arg) {
        return Cache<T, TResult>.Call(arg);
    }
}

我们创建了一个内部类 Cache,它起到了缓存的作用。.NET 泛型的奇妙之处便在于其“动态”及“区分”。“动态”在于它可以于运行时进行具体化(相对于 C++ 里的“静态”),不过目前的问题不涉及这点。而“区分”则意味着不同的具体泛型参数,在 .NET 中都是不同的类型,拥有完全分离的元数据,例如方法表(Method Table),以及静态字段等等。

这里我们便利用了这一点。由于我们只针对特定类型组合的 Cache 类型设置其 Call 字段,于是其他的类型组合自然就会直接抛出异常了。值得注意的是,也正是由于“区分”,不同的具体化类型拥有不同的元数据。因此,假如这个方法会遭遇大量非法调用的话,最好在访问 Cache<T, TResult> 之前进行类型判断,并直接抛出异常,这样可以避免产生无用的元数据。

之前有人问,为什么不可以用 Java 来实现呢?要知道 .NET 的泛型岂是 Java 的“伪泛型”可以相提并论的。

推广用法

这种做法还可以推广开来。例如在我目前的项目中用到一个第三方类库,它提供了一条条记录,我们可以读取其各字段的值,API 如下方 IRecord 接口所示:

public interface IRecord {
    string GetString(string field);
    int GetInt(string field);
    long GetLong(string field);
}

但这个 API 在使用上并不友好,我们更期望可以有一个通用的 Get<T> 方法,可以用来读取各种类型。于是我们便可以如此来编写一个扩展方法:

public static class RecordExtensions {

    private static class Cache<T> {
        public static Func<IRecord, string, T> Get;
    }

    static RecordExtensions() {
        Cache<string>.Get = (record, field) => record.GetString(field);
        Cache<int>.Get = (record, field) => record.GetInt(field);
        Cache<long>.Get = (record, field) => record.GetLong(field);
    }

    public static T Get<T>(this IRecord record, string field) {
        return Cache<T>.Get(record, field);
    }
}

事实上,使用 Lambda 表达式会生成额外的间接调用,我们直接使用 CreateDelegate 方法可以进一步降低开销。当然,这属于极小的优化,但既然不麻烦,又何乐而不为呢:

public static class ReflectionExtensions {

    public static TDelegate CreateDelegate<TDelegate>(this MethodInfo method) {
        return (TDelegate)(object)Delegate.CreateDelegate(typeof(TDelegate), method);
    }
}

public static class RecordExtensions {

    private static class Cache<T> {
        public static Func<IRecord, string, T> Get;
    }

    static RecordExtensions() {
        Cache<string>.Get = typeof(IRecord).GetMethod("GetString").CreateDelegate<Func<IRecord, string, string>>();
        Cache<int>.Get = typeof(IRecord).GetMethod("GetInt").CreateDelegate<Func<IRecord, string, int>>();
        Cache<long>.Get = typeof(IRecord).GetMethod("GetLong").CreateDelegate<Func<IRecord, string, long>>();
    }

    public static T Get<T>(this IRecord record, string field) {
        return Cache<T>.Get(record, field);
    }
}

作为一个主要工作是写基础代码给别人用的人,我还真积累了不少编写 API 的有趣经验,有机会慢慢分享。这里先来一发,那就是使用 out 关键字来减少类型信息——谁让 C# 只能通过参数进行类型推断呢?

public static class ReflectionExtensions {

    public static void CreateDelegate<TDelegate>(this MethodInfo method, out TDelegate result) {
        result = (TDelegate)(object)Delegate.CreateDelegate(typeof(TDelegate), method);
    }
}

public static class RecordExtensions {

    private static class Cache<T> {
        public static Func<IRecord, string, T> Get;
    }

    static RecordExtensions() {
        typeof(IRecord).GetMethod("GetString").CreateDelegate(out Cache<string>.Get);
        typeof(IRecord).GetMethod("GetInt").CreateDelegate(out Cache<int>.Get);
        typeof(IRecord).GetMethod("GetLong").CreateDelegate(out Cache<long>.Get);
    }

    public static T Get<T>(this IRecord record, string field) {
        return Cache<T>.Get(record, field);
    }
}

再留个问题:参考解答中的 TicksToDateTime 方法是静态方法,那假如我们需要调用的是一个实例方法又该怎么做?注意,这里不允许在每个对象的构造函数里创建独立的委托对象,因为这个操作的开销太大。创建一个静态的对象不过是一次性工作,而创建大量的对象则会造成十分可观的开销了。

没想出来?举一反三的能力必须加强啊。

其他答案

这个问题只收到的答案寥寥无几。有的比较无厘头,例如:

public class TicksToDateTimeCaller {

    private static dynamic TicksToDateTime(dynamic ticks) {
        return new DateTime(ticks);
    }

    public TResult Call<T, TResult>(T arg) {
        return (TResult)TicksToDateTime(arg);
    }
}

dynamic 怎么能解决装箱拆箱问题?一定要记住,dynamic 等同于 object,它只是在访问的时候,会由编译器生成额外负责动态调用的代码而已。事实上,这方面的开销可不仅仅是装箱拆箱,不信用 ILSpy 反编译看看?我们不能如此简单地望文生义。

还有个答案较为有趣,虽然会有额外的开销,但这个思路还是比较有参考意义的,于是这次的大奖就发给他了:

public class Parser<T, TResult> {

    public static readonly Func<T, TResult> Func = Emit();

    private static Func<T, TResult> Emit() {
        var method = new DynamicMethod(string.Empty, typeof(TResult), new[] { typeof(T) });
        var il = method.GetILGenerator();
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Ret);
        return (Func<T, TResult>)method.CreateDelegate(typeof(Func<T, TResult>));
    }
}

public class TicksToDateTimeCaller {

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

    public TResult Call<T, TResult>(T arg) {
        return Parser<DateTime, TResult>.Func(TicksToDateTime(Parser<T, long>.Func(arg)));
    }
}

还有个同学给出了类似的做法,他受到 StackOverflow 上这个问题的启发,给出了如下的做法:

private static class Cache<T, TResult> {

    public static Func<T, TResult> Call;

    static Cache() {
        Cache<long, DateTime>.Call = TicksToDateTime;
    } 
}

可惜的是,初始化 Call 的代码放错了位置,且经过提醒还是未能发现问题。我认为这意味着并没有完全搞清楚代码的机制,因此只能给个小奖了。

最后,欢迎大家关注我的公众账号“赵人希”,有技术有生活,深入刻画了本人在娱乐圈内的起起伏伏

Creative Commons License

本文基于署名 2.5 中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名赵劼(包含链接),具体操作方式可参考此处。如您有任何疑问或者授权方面的协商,请给我留言

Add your comment

49 条回复

  1. oger
    118.186.196.*
    链接

    oger 2014-05-29 00:15:09

    创建的委托使用静态变量就起到了缓存的作用。果然高!

  2. cdjboy
    171.221.153.*
    链接

    cdjboy 2014-05-29 12:33:07

    强势围观超帅归来……

  3. tinytian
    221.221.150.*
    链接

    tinytian 2014-05-29 14:19:40

    老赵也开始在英文和汉字之间插入空格了。

  4. 老赵
    admin
    链接

    老赵 2014-05-29 20:11:24

    @tinytian

    观察的真仔细啊。

  5. HackGary
    125.95.19.*
    链接

    HackGary 2014-05-30 09:54:52

    老赵,按你的推广方法,假如方法是private,并且泛型字典必须写在辅助类里,该怎么改?

  6. 老赵
    admin
    链接

    老赵 2014-05-30 14:32:40

    @HackGary

    举个例子?

  7. HackGary
    125.95.19.*
    链接

    HackGary 2014-05-30 14:41:51

    例如我定义了一个接口 ICardReader,里面有一个方法 T Read(),辅助类 CardReaderExtensions 定义了你推广方法里面的泛型字典,在类型初始化器内,我该如何将此接口实现类的私有方法注册到泛型字典委托上?

  8. devin
    124.74.26.*
    链接

    devin 2014-05-30 18:05:38

    赵姐夫,我用你写的CodeTimer比较过Cache版本和Box/Unbox版本,Release模式下,box/unbox版本效率更高是何解?

    在迭代1000 * 1000次时:

    cache版本:
        Time Elapsed:   37ms
        CPU Cycles:     101,278,331
    
    box/unbox版本:
        Time Elapsed:   32ms
        CPU Cycles:     91,454,416
    

    我看了生成的IL,cache版本会有ldsfld和callvirt指令。这两个指令会比两次box和两次unbox开销更大么?请问应该如何取舍?

  9. 老赵
    admin
    链接

    老赵 2014-05-30 23:18:46

    @devin

    仔细看这个题目的说明,仔细看。

  10. 老赵
    admin
    链接

    老赵 2014-05-30 23:20:58

    @HackGary

    还是不懂,写完整点吧,带示例代码的那种。

  11. HackGary
    125.95.19.*
    链接

    HackGary 2014-05-31 08:44:34

    public interface ICardReader
    {
       T Read<T>()
    }
    
    internal class CustomCardReader : ICardReader
    {
       public T Read<T>()
       {
           return this.ReadCard<T>();
       }
    
       private IdCardInfo ReadCard()
       {
           return new IdCardInfo
       }
    }
    
    public static class CardReaderExtensions
    {
        public static class Cache<T>
        {
            public static Func<ICardReader, T> Call;
        }
    
        static CardReaderExtensions()
        {
            //这里怎么将CustomCardReader内的ReadCard方法放入泛型字典Cache<IdCardInfo>.Call
        }
    
        public static T ReadCard<T>(this ICardReader cardReader)
        {
            return Cache<T>.Call(cardReader);
        }
    }
    
  12. 老赵
    admin
    链接

    老赵 2014-06-02 16:29:30

    @HackGary

    举个正常点的例子好吗,本来扩展方法就是封装公开方法的,比如你上面的 Read<T> 方法(虽然它已经泛型了),你挖一个私有方法出来做什么。假如你非要调用私有方法,那么平时该怎么做就怎么做咯,反射之类的都行。

  13. HackGary
    113.72.163.*
    链接

    HackGary 2014-06-02 16:56:57

    上面的例子确实是个不好的设计,泛型字典应该放在一个基类CardReaderBase更合理!

    假如又有其他子类实现了这个接口,Read<T>()方法都分别返回不同的类型,那我只能分别在子类Read<T>()方法上调用(T)(object)子类实例.方法()吗?能否分别在子类的类型构造器中,将具体方法绑定到基类CardReaderBase的泛型字典中?反射应该不行吧,毕竟要传入当前调用的对象实例...

  14. 老赵
    admin
    链接

    老赵 2014-06-02 21:46:43

    @HackGary

    描述地那么复杂,我还是听不懂,就不能自觉地给些代码吗……说不定代码写出来以后,你就知道可以怎么做,或者就像你上面举的例子,完全就是一个很奇怪的问题……

    说到底,你怎么期望调用私有方法,这就是很奇怪的目的。不管是让扩展方法调用私有方法,或是让父类调用子类的私有方法什么的,从一开始就是不该这么做。

    你先把问题搞清楚,再来谈这个方法可以怎么用。

  15. HackGary
    113.72.163.*
    链接

    HackGary 2014-06-02 22:12:23

    public interface ICardReader
    {
        void Open();
        T Read<T>();
        void Close();
    }
    
    internal class CustomCardReader : ICardReader
    {
        public void Open(){...}
    
        public T Read<T>()
        { 
           return (T)(object)new IdCardInfo();
        }
    
        public void Close(){...}
    }
    
    internal class Custom2CardReader : ICardReader
    {
        public void Open(){...}
    
        public T Read<T>()
        { 
           return (T)(object)new CardInfo();
        }
    
        public void Close(){...}  
    }
    

    @老赵

    难道我每个子类的Read方法都要这样写吗(T)(object)? 不能用泛型字典来优化下编码体验?

  16. 老赵
    admin
    链接

    老赵 2014-06-04 22:35:38

    @HackGary

    虽然完全看不懂你这个代码有什么意义,但编码体验还能怎么更优?

    话说你到底看懂RecordExtensions做了什么事情了吗?它是把N个具体类型的方法统一到同一个泛型方法上,那么你这里究竟是要做什么?

  17. 链接

    Ivony 2014-06-14 00:18:27

    呃,,,,,其实你这个叫做泛型的特化。

    哎现在的年轻人都不喜欢翻经典,类型字典和处理泛型的特化在装配脑袋几年前的博客就讨论的很清楚了,随便翻翻就能赢奖品啊。

  18. 老赵
    admin
    链接

    老赵 2014-06-14 14:33:11

    @Ivony

    应该说是用“泛型特化”来解决现在的问题。其实我几年前也提过,估计大部分人看过就忘。

  19. simplejoy
    221.237.117.*
    链接

    simplejoy 2014-06-26 16:47:40

    问句题外话,博主的代购还有没有在进行呢?

  20. bj95
    175.152.176.*
    链接

    bj95 2014-06-29 23:06:24

    为什么windows live账号输入密码后,一直没有跳转回来。

  21. 老赵
    admin
    链接

    老赵 2014-06-30 11:34:17

    @simplejoy

    这方面问题看微博置顶,博客不管这方面……

  22. bj95
    175.152.176.*
    链接

    bj95 2014-06-30 18:21:37

    public class TicksToDateTimeCaller
    {
        private static class Cache<T, TResult>
        {
            public static Func<TicksToDateTimeCaller, T, TResult> Call;
        }
    
        private DateTime TicksToDateTime(long ticks)
        {
            return new DateTime(ticks);
        }
    
        static TicksToDateTimeCaller()
        {
            Cache<long, DateTime>.Call = (a, b) => a.TicksToDateTime(b);
        }
    
        public TResult Call<T, TResult>(T arg)
        {
            return Cache<T, TResult>.Call(this, arg);
        }
    }
    

    实例方法可以这样吧?

  23. 老赵
    admin
    链接

    老赵 2014-07-02 22:18:25

    @bj95

    不错。

  24. ffipp
    65.49.2.*
    链接

    ffipp 2014-07-08 13:08:26

    public class TicksToDateTimeCaller {
    
        private static class Cache<T, TResult> {
            public static Func<T, TResult> Call;
        }
    
        private DateTime TicksToDateTime(long ticks) {
            return new DateTime(ticks);
        }
    
        static TicksToDateTimeCaller() {
            TicksToDateTimeCaller caller =new TicksToDateTimeCaller(); 
            Cache<long, DateTime>.Call = caller.TicksToDateTime;
        }
    
        public TResult Call<T, TResult>(T arg) {
            return Cache<T, TResult>.Call(arg);
        }
    }
    

    这种岂不是最直接明了?

  25. 老赵
    admin
    链接

    老赵 2014-07-08 16:02:27

    @ffipp

    真不知道为什么你把一个静态方法变成了成员方法就“最直接明了”了。

  26. 链接

    杨飞 2014-07-24 15:17:42

    为什么老赵的博客不能用新浪微博或QQ登陆,Live 登录老慢.

  27. 链接

    张占岭 2014-09-09 16:14:49

    @oger

    静态的泛型类每次在使用时都会进行访问的,这个静态元素在这里应该起不到缓存的作用的.

  28. Scan
    119.130.187.*
    链接

    Scan 2014-11-16 23:13:56

    我觉得这样比较好:

    public class FuncCaller: ICaller {
            public Delegate Func { get; set; }
            public TResult Call<T, TResult>(T arg) {
                return (Func as Func<T, TResult>)(arg);
            }
        }
    
    
    Func<long, DateTime> ticksToDatetime = ticks => new DateTime(ticks);
    var dt = new FuncCaller() { Func = ticksToDatetime }.Call<long, DateTime>(1234);
    
  29. 老赵
    admin
    链接

    老赵 2014-11-16 23:28:18

    @Scan

    你这做法除了蛋疼到爆,没看出哪里好……

  30. Scan
    119.130.187.*
    链接

    Scan 2014-11-17 05:34:53

    好,我解释为什么我觉得更好

    1. 方案比你简洁
    2. 我的是运行时绑定,你是编译期绑定;我通过delegate来重写行为,你通过继承来重写行为,如果不是对象量很大的OO场合,性能不敏感,则lambda几乎总比overrdie简洁
    3. 如果需要你同时支持多组“int=>int”的Caller,你的方案还需要调整;我的很灵活,直接上,性能却和你一样
    4. 没有引入专门的顶层符号,需要特化不同行为的多个ICaller,只需要传入不同的lambda,你要写不同的类
    5. 可重入;是用过即扔,还是放在静态字段中长期持有,用户决定

    你只是想绕过C#的泛型约束的限制,却用了更复杂的trick,比如你那个不用做泛型的泛型类Cache

    你一直在强调为避免装箱/拆箱开销,不能用类型擦除来绕过泛型约束。 相比你在参数/返回值上的类型擦除尝试,我只是把文章做在了delegate上,而委托本来是引用类型,类型擦除无开销,所以我的方案一步到位

    没想吵,不过你的口气让人不舒服,还你

    “你这做法除了蛋疼到爆,没看出哪里好……”

  31. Scan
    119.130.187.*
    链接

    Scan 2014-11-17 12:34:18

    你的方案,要满足下面的需求,还需要做多少事?

    1. 添加ParseInt(string=>int)、IntToString(int=>String)、DatetimeToTicks(Datetime=>long)等,你的方案,还需要特化多少个Caller? 好,简单点,你可以让Cache.Call从外部接受Delegate,那么,如果再追加线程安全的要求呢?你的Cache.Call要么和类型绑死(你使用的静态构造),要么不可重入

    2. 要求支持Abs(float=>float)、Sqr(float=>float)、Sqrt(float=>float)、Sin(float=>float)等一票源类型相同、目标类型也相同的变换,你不需要特化多个Caller?

    要支持上面的“以任意行为来特化ICaller”、“线程安全”这些很正常的需求,我的方案一行不改,以行为的Delegate构造多个FuncCaller实例即可

    不过实际上的确比你的方案慢一点,那句“Func as Func<T, TResult>”需要运行时类型检查,而你的方案将行为和类型做静态绑定了,没有运行时开销

  32. 老赵
    admin
    链接

    老赵 2014-11-19 21:55:59

    @Scan

    你是在搞笑吗,你拿到一个ticksToDatetime不直接用,创建一个对象先把它cast成弱类型再cast回来,再调用一次?

    这不叫“蛋疼到爆”还叫什么,更何况你怎么知道对方要用的是DateTime => long这组类型?

    还有什么叫做我用继承来重写行为,这里哪里涉及到继承了?还有什么多组int => int什么线程安全什么特化Caller都是什么和什么啊。

    你说了一堆看上去很高级的东西,但我根本就不懂你想说什么,还是你根本没明白现在想要解决的是什么问题?

  33. Scan
    119.130.184.*
    链接

    Scan 2014-11-20 02:05:46

    我是这样理解你的问题的:

    从最终目的来说,你需要的是函数“TicksToDatetime”被调用,但你没有直接进行“TicksToDatetime()”,而是实现“ICaller”,最终经由“ICaller.Call<long, DateTime>()”来调用;所以,我认为,调用方不希望知道“TicksToDatetime”的存在,它只知道接口“ICaller”,而我们关心它使用“ICall”的方式是“ICall.Call<long, DateTime>()”

    所以,这里的问题是,对于调用方对“ICaller”类型的“ICaller.Call<T, TResult>()”使用方式,令类型吻合“Func<T, TResult>”的特定函数“F”被调用

    所以我认为,这里的要解决的问题,就是一个适配,提供以“ICaller”为媒介调用任何函数函数的能力;即,如果有一组“ICaller”,它们的运行时类型不但可以是各种“ConcreteCaller”,也可以是“Func<T, TResult>”的Adapter,使得“ICaller”的持有者可以不加区别的通过“Call<T, TResult>()”的方式,访问各种“ConcreteCaller”以及各种签名吻合“Func<T, TResult>”的函数提供的服务。

    这里的障碍是,只能以泛型方法的方式去实现“Call<T, TResult>”,而C#语言不支持同时提供泛型“Call<T, TResult>”和特化“Call<long, Datetime>”两个版本然后在调用点上进行重载(如果支持的话,前者直接“return default(TResult)”,后者直接调用“TicksToDatetime”)。

    既然C#语法要求你必须去实现泛型方法,而你手里又只有具体类型,所以一个直观的方案就是,先对泛型做类型擦除“T=>object”(如java的泛型),再由“object=>long”得到具体类型以匹配手中的实现。方法很直观,但正如你提到的,不考虑优化的话,有无谓的boxing/unboxing开销,不够好。

    然后就是你最后的方案,和我的方案了。

    我就觉得,既然目标是要让形如“TicksToDatetime”的各种函数可以被访问,而又受限于只能通过“ICaller”来访问,那么,我那样一个类型适配器就够了。

    你是在搞笑吗,你拿到一个ticksToDatetime不直接用,创建一个对象先把它cast成弱类型再cast回来,再调用一次?

    我是在做接口适配,否则,我们为什么非要把TicksToDatetime塞到ICaller里面去、而不是直接“TicksToDatetime()”?

    更何况你怎么知道对方要用的是DateTime => long这组类型?

    ICall的持有者可以以任何类型调用“Call”,但是我们只关注“Call<long, Datetime>”的用法;他持有“ICaller”对象的运行时类型是你的“TicksToDateTimeCaller”和我的“new FuncCaller() { Func = ticksToDatetime }”时,都只能以“Call<long, Datetime>()”的方式来访问,其他类型的“Call”调用都是非法的,你的方案会访问到“Cache.Call”中的空引用,我的方案会抛出异常。这种行为是合理的,在类型不兼容时我们拒绝服务;假如用户明确指出,类型不匹配的时候应该忽略,在你我的实现中加一句判空即可。

    还有什么叫做我用继承来重写行为,这里哪里涉及到继承了?

    是我用词不对,C#里面不叫继承,但我指的就是用“TicksToDateTimeCaller”实现“ICaller”的做法。 我认为需要的只是将“TicksToDatetime”的服务透明的暴露给“ICaller”的使用方,像我那样给一个支持任意类型函数的Adapter就够了;而你的方案中,为了让“TicksToDatetime”可用,引入了一个专门的类型“TicksToDateTimeCaller”,即,一个实现“ICaller”接口的类型,仅仅服务于特定函数“TicksToDatetime”;那么,如果需要让你透过“ICaller”调用“DatetimeToTicks”呢?难道要再写一个实现“ICaller”接口的“DatetimeToTicksCaller”类型?

    当然,你可以将“TicksToDateTimeCaller”改个名,然后让它接收委托,并赋值给“Cache.Call”,这样就可以复用同一个“ICaller”的实现类,调用不同的方法了;但是,既然你的“Cache.Call”是静态字段,就又引入可重入问题,因为必须支持在任意时间将委托赋值给静态字段“Cache.Call”,这就得加锁。另外的问题,在你只提供一个“ICaller”实现类的情况下,我有两个类型签名相同的函数,都是“Func<int, float>”,那么,你怎样将它们装入“ICaller[2]”?毕竟这两个类型签名相同的函数,都只能放入“IntToFloatCaller.Cache<int, float>.Call”中

  34. Scan
    119.130.184.*
    链接

    Scan 2014-11-20 02:15:19

    嗯,如果说,你的目的是,拿到一个“ICaller”,然后以“Call<int, float>()”、“Call<float, int>()”、“Call<long, Datetime>()”进行一系列调用,只有类型匹配的最后一次调用生效,而其他调用因为类型不匹配而无声息的话...

    你原始的例子中,类型不匹配,断言失败。 你最后的例子中,类型不匹配,空引用。

    好吧,我姑且认为你想要做的确实是只对特定类型反应(既然不多态,为何还用接口?只为了提供不同的实现?既然有lambda了,何须继承/实现接口),还是那句话,我的方案,"as"过后一句判空即可

  35. 老赵
    admin
    链接

    老赵 2014-11-20 10:23:01

    @Scan

    嗯,如果说,你的目的是,拿到一个“ICaller”,然后以Call<int, float>()Call<float, int>()Call<long, Datetime>()进行一系列调用……

    没错

    只有类型匹配的最后一次调用生效,而其他调用因为类型不匹配而无声息的话...

    什么叫做“有类型匹配的最后一次调用生效”,当然每个组合都要生效。什么叫做“因为类型不匹配而无声息的话”,类型不匹配当然要给你反映,比如异常。

    ICaller接口只是给你做题用的提干而已,不是解决方案。你没看到后面的例子么?哪儿来接口?哪来为每个类型组合实现一个ICaller

    你到底写那么多东西出来的时候有没有好好看文章,要不你继续浪费你的时间好了,但你写再多我也不打算看了。

  36. Scan
    183.2.40.*
    链接

    Scan 2014-11-20 11:50:31

    好的,打扰了

  37. MC幻牙
    111.37.0.*
    链接

    MC幻牙 2014-12-04 00:29:09

    不好意思哈,在这里问一个不相关的问题。现在正在用您在博客园设计的模板,看您的博客感觉特别清爽,尤其是代码的样式——我曾试着做出类似的效果,但最终都以失败告终,尤其是左侧那条竖直的较之代码插入模板要宽的绿色长条,而我又特别追求阅读体验,对于外观丑陋的博客阅读兴致便降了大半,因此能烦请您不吝告知如何能在代码左侧显示绿色长条吗?

  38. Poiuyt
    61.144.61.*
    链接

    Poiuyt 2014-12-16 16:01:16

    我正好用上,之前在写自己的框架时用到了IQueryable实现延迟加载,里头IProvider的Execute方法就有这个让我头疼的地方了

    public TResult Execute<TResult>(Expression expression)
        {
            Expression<Func<T, bool>> result = expression as Expression<Func<T, bool>>;
            List<T> source = new DBSql().FindAs<T>(result);
            dynamic _temp = source;
            TResult r = (TResult)_temp;
            return r;
        }
    

    现在改成

    public class Cache<T2>
        {
            public static Func<Expression<Func<T, bool>>, T2> Conver;
        }
    
        public TResult Execute<TResult>(Expression expression)
        {
            Expression<Func<T, bool>> result = expression as Expression<Func<T, bool>>;
            Cache<List<T>>.Conver = x => new DBSql().FindAs<T>(x);
            TResult r = Cache<TResult>.Conver(result);
            return r;
        }
    
  39. Poiuyt
    61.144.61.*
    链接

    Poiuyt 2014-12-16 17:13:19

    至于最后留的问题

    public class TicksToDateTimeCaller
        {
            private static class Cache<T, TResult>
            {
                public static Func<T, TResult> Call;
            }
            private DateTime TicksToDateTime(long ticks)
            {
                return new DateTime(ticks);
            }
            private DateTime TicksToDateTime(string ticks)
            {
                return DateTime.Now;
            }
            public TicksToDateTimeCaller()
            {
    
            }
            public TResult Call<T, TResult>(T arg)
            {
                Func<T, TResult> func = Cache<T, TResult>.Call;
                if (func == null)
                {
                    MethodInfo method = typeof(TicksToDateTimeCaller).GetMethod("TicksToDateTime", BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[] { typeof(T) }, null);
                    func = Delegate.CreateDelegate(typeof(Func<T, TResult>), null, method) as Func<T, TResult>;
                    Cache<T, TResult>.Call = func;
                }
                return func(arg);
            }
        }
    

    这样如何。。。。。

  40. 链接

    刘丰 2014-12-31 09:10:00

    对于最后留下的问题, 我想到的是单例: private static TicksToDateTimeCaller _instance = new TicksToDateTimeCaller();

    static TicksToDateTimeCaller()
        {
            Cache<long, DateTime>.Call = _instance.TicksToDataTime;
        }
    
  41. linustd
    27.209.251.*
    链接

    linustd 2015-03-11 20:14:09

    哈哈哈啊, 为了让.net 自动生成一个特定类型的泛型类/方法,而写这么一堆代码,有意思吗?

    想对常用的几个泛型类型特殊对待,完全可以看看Convert类的实现,直接把常用的类型的方法硬编码写出来,让编译器编译时自动链接到最优化的实现方法就行。

    .net作为一个喜欢封装的平台,微软封装了它,这里有解开它,等于走了100米,又反方向走100米,功夫浪费了不少,却没效果。

    还是那句话,.net 不行,搞.net的不是傻子就是变态!

  42. 老赵
    admin
    链接

    老赵 2015-03-20 01:08:21

    @linustd

    你好弱,概念一塌糊涂……

  43. Ray
    124.79.212.*
    链接

    Ray 2015-03-22 23:42:27

    求微信公众号

  44. darrenji
    140.75.201.*
    链接

    darrenji 2015-04-25 09:34:07

    谢谢侬,收获颇大。

  45. ChuckLu
    180.173.8.*
    链接

    ChuckLu 2015-04-30 20:06:42

    使用这个的意义在哪里呢? 类型是无限多,每一个类型,都要写一个独立的方法?

    RecordExtensions.Get(rec, string.Empty); 题目中的T对应于int,long,string 我传递一个A类型,直接抛出异常了。

    难道用约束吗?

  46. Dong
    122.118.64.*
    链接

    Dong 2015-05-04 12:52:20

    阅读完您的文章真的受益良多,可以请问最後的问题方法这样可以吗?

    public class TicksToDateTimeCaller
    {
        private static class Type<T, toTResult>
        {
            public static Func<T, toTResult> Change;
    
            static Type()
            {
                Type<long, long>.Change = x => x;
                Type<DateTime, DateTime>.Change = x => x;
            }
        }
    
        public TResult TicksToDateTime<T, TResult>(T ticks)
        {
            return Type<DateTime, TResult>.Change(new DateTime(Type<T, long>.Change(ticks)));
        }
    }
    
  47. 175114
    60.207.51.*
    链接

    175114 2015-11-04 02:44:33

    老赵,你好好休息,怎么经常熬夜啊。http://www.175114.com/

  48. zoroseo2020
    124.156.224.*
    链接

    zoroseo2020 2020-10-16 00:17:31

    分析很清楚,谢谢 彩票 福彩双色球 幸运飞艇

  49. Oliver
    120.29.69.*
    链接

    Oliver 2022-08-26 00:45:34

    It is safe to say that Xenoblade Chronicles 3 is the most dystopian game in the Xenoblade series because it is set in a future society that has been ripped apart by conflict. The focus of the game is on the continual conflict between the two groups Keves and Agnus. Young troops that were made artificially and given 10 years to live—referred to in-game as "terms"—fight in these fights. However, due to their brutal way of existence, very few of them even survive that long. Off-Seers are specialized units that exist on both Keves and Agnus and are in charge of playing a specific song to convey the spirits of slain soldiers to the great beyond.

    Read about the PC version of Among Us

发表回复

登录 / 登录并记住我 ,登陆后便可删除或修改已发表的评论 (请注意保留评论内容)

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

评论内容(大于5个字符):

  1. Your Name yyyy-MM-dd HH:mm:ss

使用Live Messenger联系我