• 浅谈c#泛型类型变量作为操作数使用的通用解决方法


    问题来源:在c#编程中,经常会对一些数字型基元类型如Int16,Int32,Int64,Decimal等,做一些加减乘除的事情。比如我们经常写出下面的方法,用来计算从0开始到输入的32位整数之间数字的总和(没有考虑溢出等特殊情况):

      internal static Int32 Sum(int num)
            {
                Int32 sum = 0;
                for (Int32 i = 0; i < num; i++)
                {
                    sum += i;
                }
                return sum;
            }
    

    同理,如果我们需要返回类型和输入类型为其他基元类型,如Decimal,Int64等等,正常情况下我们都会修改上述代码中返回和输入参数类型,比如可以这样对Int64进行求和:

            internal static Int64 Sum(Int64 num)
            {
                Int64 sum = 0;
                for (Int64 i = 0; i < num; i++)
                {
                    sum += i;
                }
                return sum;
            }
    

    但是这样有多少个不同的基元数字类型是不是就要写多少个这样的方法(这里不考虑某些数字型基元类型之间的相互转换),造成方法膨胀,代码臃肿?大家分析上面的代码,发现参数个数,运算格式非常类似,毫无疑问会想到抽象出一个公共的泛型方法来解决数字型基元类型的运算问题。

    在<<CLR Via C#>>一书中,Jeffrey Richter就举例并“千方百计”的想实现下面的泛型方法来实现泛型类型变量作为操作数的通用解决方案:

            internal static class UsingGenericTypeVariablesAsOperands
            {
                private static T Sum<T>(T num) where T : struct
                {
                    T sum = default(T);
                    for (T n = default(T); n < num; n++)
                        sum += n;
                    return sum;
                }
            }
    

    但是,编译器无情地给出了如下错误报告:

    错误    9    运算符“++”无法应用于“T”类型的操作数  

    错误    10    运算符“+=”无法应用于“T”和“T”类型的操作数  

    错误    8    运算符“<”无法应用于“T”和“T”类型的操作数 

    Jeffrey Richter大大在书中希望MS在CLR和编译器未来的版本中解决这个问题,也就是说通过泛型对当前的这个问题还无解。书中还说,解决这个方法可能需要通过性能较差的反射或者是操作符重载,我自己没有实现过,网上google未果,我自己尝试了一种最笨的方法:

            internal static class UsingGenericTypeVariablesAsOperands
            {
                private static T Sum<T>(T num) where T : struct
                {
                    T sum = default(T);
                    Type type = num.GetType();
                    switch (type.Name)
                    {
                        default:
                            break;
    
                        case "Int32":
                            Int32 result = 0;
                            for (Int32 i = 0; i < (Int32)(object)num; i++)
                            {
                                result += i;
                            }
                            sum = (T)(Object)result;
                            break;
                        /*......还有其他类型不一一列举......*/
                    }
                    return sum;
                }
            }
    

    估计肯定不是大牛口中的反射实现方法,不知道大家有没有好的替代方案。老实说,上面的代码看着真他大爷的,虽然看起来很简单,可是实现起来真是费力不讨好。

    附:

    sealed接口方法在派生类中的实现

    下面的代码摘录自<<CLR via C#>>。

    InterfaceReimplementation

    解释:如果一个接口方法是sealed的,派生类不能重写它,在上述代码中,Base类的Dispose方法是隐式密封的,不能被派生类Derived 重写。那么Derived 类如何实现IDisposable接口的Dispose方法呢?原来派生类可以重新继承同一个接口,并且可为该接口的方法提供它自己的实现。在一个对象上调用一个接口的方法时,将调用该方法在该对象的类型中的实现。

    显式接口方法实现(Explicit Interface Method Implementation,EIMI)

    实例为尊,代码说话。我们定义一个继承自IDisposable接口的一个类SimpleType,然后调用Dispose方法:

    ExplicitInterfaceMethodImpl

    在第一次调用Dispose方法时,SimpleType的Dispose方法被调用,这个很好理解,输出“Dispose”。然后我们定义的IDisposable类型变量d初始化为引用SimpleType对象,然后d调用Dispose方法时,从原则上来说,应该就是调用IDisposable接口的Dispose方法。由于c#要求公共Dispose方法还必须是IDisposable的Dispose方法的实现,所以会执行和st的Dispose方法一样的代码,并输出“Dispose”。我们将上面的代码改进如下:

    ExplicitInterfaceMethodImpl


    第一个方法输出“Dispose”,第二个输出“IDisposable Dispose”,看起来挺奇怪的。在SimpleType类里定义的 IDisposable.Dispose()方法,就是一个EIMI,这个EIMI实现不允许指定可访问性,比如public等,但是编译器在生成方法的元数据时,其可访问性会被自动设为private,由此我们可以猜测到EIMI不能在派生类型中调用。实际上EIMI是极其不好理解的,它有比较多的使用限制,比如值类型和接口的转换需要装箱等等,实际开发中慎用就是了。

  • 相关阅读:
    PHP 中的魔法常数
    Mac上tesseract-OCR的安装配置
    Java设计模式系列之迭代器模式
    Java设计模式系列之策略模式
    设计模式
    Java设计模式系列之单例模式
    SQl Server 中登录名 、用户、角色、概念一览
    数据结构知识点总结
    面试题收集——Java基础部分(一)
    XML学习总结(一)——XML介绍
  • 原文地址:https://www.cnblogs.com/jeffwongishandsome/p/1870832.html
Copyright © 2020-2023  润新知