浅谈c#泛型类型变量作为操作数使用的通用解决方法
问题来源:在c#编程中,经常会对一些数字型基元类型如Int16,Int32,Int64,Decimal等,做一些加减乘除等等运算的事情。比如我们经常写出下面的方法,用来计算从0开始到输入的32位整数之间数字的总和(没有考虑溢出等特殊情况):
1 | internal static Int32 Sum( int num) |
2 |
|
3 | Int32 sum = 0; |
4 | for (Int32 i = 0; i < num; i++) |
5 |
|
6 | sum += i; |
7 |
|
8 | return sum; |
9 | } |
同理,如果我们需要返回类型和输入类型为其他基元类型,如Decimal,Int64等等,正常情况下我们都会修改上述代码中返回和输入参数类型,比如可以这样对Int64进行求和:
1 | internal static Int64 Sum(Int64 num) |
2 |
|
3 | Int64 sum = 0; |
4 | for (Int64 i = 0; i < num; i++) |
5 |
|
6 | sum += i; |
7 |
|
8 | return sum; |
9 | } |
但是这样有多少种不同的基元数字类型是不是就要写多少个这样的方法(这里不考虑某些数字型基元类型之间的相互转换),造成方法膨胀,代码臃肿?大家分析上面的代码,发现参数个数,运算格式非常类似,毫无疑问会想到抽象出一个公共的泛型方法来解决数字型基元类型的运算问题。
在<<CLR Via C#>>一书中,Jeffrey Richter就举例并“千方百计”的想实现下面的泛型方法来实现泛型类型变量作为操作数的通用解决方案:
01 | internal static class UsingGenericTypeVariablesAsOperands |
02 |
|
03 | private static T Sum<T>(T num) where T : struct |
04 |
|
05 | T sum = default (T); |
06 | for (T n = default (T); n < num; n++) |
07 | sum += n; |
08 | return sum; |
09 |
|
10 | } |
但是,编译器无情地给出了如下错误报告:
错误 9 运算符“++”无法应用于“T”类型的操作数
错误 10 运算符“+=”无法应用于“T”和“T”类型的操作数
错误 8 运算符“<”无法应用于“T”和“T”类型的操作数
Jeffrey Richter在书中希望MS在CLR和编译器未来的版本中解决这个问题,也就是说通过泛型对当前的这个问题还无解。书中还说,解决这个方法可能需要通过性能较差的反射或者是操作符重载,我自己没有实现过,网上google未果,自己动手尝试了一种最笨的方法:
01 | internal static class UsingGenericTypeVariablesAsOperands |
02 |
|
03 | private static T Sum<T>(T num) where T : struct |
04 |
|
05 | T sum = default (T); |
06 | Type type = num.GetType(); |
07 | switch (type.Name) |
08 |
|
09 | default
|
10 | break
|
11 | |
12 | case "Int32"
|
13 | Int32 result = 0; |
14 | for (Int32 i = 0; i < (Int32)( object )num; i++) |
15 |
|
16 | result += i; |
17 |
|
18 | sum = (T)(Object)result; |
19 | break
|
20 | /*......还有其他类型不一一列举......*/ |
21 |
|
22 | return sum; |
23 |
|
24 | } |
估计肯定不是大牛口中的反射实现方法,不知道大家有没有好的替代方案。老实说,上面的代码看着真他大爷的,虽然看起来很简单,可是实现起来真是费力不讨好,还是不用为好。
附:关于sealed接口方法在派生类中的实现
下面的代码摘录自<<CLR via C#>>。
01 | internal static class InterfaceReimplementation |
02 |
|
03 | public static void Main() |
04 |
|
05 | /************************* First Example *************************/ |
06 | Base b = new Base(); |
07 | |
08 | // Call's Dispose by using b's type: "Base's Dispose" |
09 | b.Dispose(); |
10 | |
11 | // Call's Dispose by using b's object's type: "Base's Dispose" |
12 | ((IDisposable)b).Dispose(); |
13 | |
14 | |
15 | /************************* Second Example ************************/ |
16 | Derived d = new Derived(); |
17 | |
18 | // Call's Dispose by using d's type: "Derived's Dispose" |
19 | d.Dispose(); |
20 | |
21 | // Call's Dispose by using d's object's type: "Derived's Dispose" |
22 | ((IDisposable)d).Dispose(); |
23 | |
24 | |
25 | /************************* Third Example *************************/ |
26 | b = new Derived(); |
27 | |
28 | // Call's Dispose by using b's type: "Base's Dispose" |
29 | b.Dispose(); |
30 | |
31 | // Call's Dispose by using b's object's type: "Derived's Dispose" |
32 | ((IDisposable)b).Dispose(); |
33 |
|
34 | |
35 | // This class is derived from Object and it implements IDisposable |
36 | internal class Base : IDisposable |
37 |
|
38 | // This method is implicitly sealed and cannot be overridden |
39 | public void Dispose() |
40 |
|
41 | Console.WriteLine( "Base's Dispose" ); |
42 |
|
43 |
|
44 | |
45 | // This class is derived from Base and it re-implements IDisposable |
46 | internal class Derived : Base, IDisposable |
47 |
|
48 | // This method cannot override Base's Dispose. 'new' is used to indicate |
49 | // that this method re-implements IDisposable's Dispose method |
50 | new public void Dispose() |
51 |
|
52 | Console.WriteLine( "Derived's Dispose" ); |
53 | |
54 | // NOTE: The next line shows how to call a base class's implementation (if desired) |
55 | // base.Dispose(); |
56 |
|
57 |
|
58 | } |
解释:如果一个接口方法是sealed的,派生类不能重写它,在上述代码中,Base类的Dispose方法是隐式密封的,不能被派生类Derived 重写。那么Derived 类如何实现IDisposable接口的Dispose方法呢?原来派生类可以重新继承同一个接口,并且可为该接口的方法提供它自己的实现。在一个对象上调用一个接口的方法时,将调用该方法在该对象的类型中的实现。