問題來源:在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#>>。
internal static class InterfaceReimplementation{ public static void Main() { /************************* First Example *************************/ Base b = new Base(); // Call's Dispose by using b's type: "Base's Dispose" b.Dispose(); // Call's Dispose by using b's object's type: "Base's Dispose" ((IDisposable)b).Dispose(); /************************* Second Example ************************/ Derived d = new Derived(); // Call's Dispose by using d's type: "Derived's Dispose" d.Dispose(); // Call's Dispose by using d's object's type: "Derived's Dispose" ((IDisposable)d).Dispose(); /************************* Third Example *************************/ b = new Derived(); // Call's Dispose by using b's type: "Base's Dispose" b.Dispose(); // Call's Dispose by using b's object's type: "Derived's Dispose" ((IDisposable)b).Dispose(); } // This class is derived from Object and it implements IDisposable internal class Base : IDisposable { // This method is implicitly sealed and cannot be overridden public void Dispose() { Console.WriteLine("Base's Dispose"); } } // This class is derived from Base and it re-implements IDisposable internal class Derived : Base, IDisposable { // This method cannot override Base's Dispose. 'new' is used to indicate // that this method re-implements IDisposable's Dispose method new public void Dispose() { Console.WriteLine("Derived's Dispose"); // NOTE: The next line shows how to call a base class's implementation (if desired) // base.Dispose(); } }}
解釋:如果一個介面方法是sealed的,衍生類別不能重寫它,在上述代碼中,Base類的Dispose方法是隱式密封的,不能被衍生類別Derived 重寫。那麼Derived 類如何?IDisposable介面的Dispose方法呢?原來衍生類別可以重新繼承同一個介面,並且可為該介面的方法提供它自己的實現。在一個對象上調用一個介面的方法時,將調用該方法在該對象的類型中的實現。
顯式介面方法實現(Explicit Interface Method Implementation,EIMI)
執行個體為尊,代碼說話。我們定義一個繼承自IDisposable介面的一個類SimpleType,然後調用Dispose方法:
internal static class ExplicitInterfaceMethodImpl{ public sealed class SimpleType : IDisposable { public void Dispose() { Console.WriteLine("Dispose"); } } public static void Main() { SimpleType st = new SimpleType(); // This calls the public Dispose method implementation st.Dispose(); // This calls IDisposable's Dispose method implementation IDisposable d = st; d.Dispose(); }}
在第一次調用Dispose方法時,SimpleType的Dispose方法被調用,這個很好理解,輸出“Dispose”。然後我們定義的IDisposable類型變數d初始化為引用SimpleType對象,然後d調用Dispose方法時,從原則上來說,應該就是調用IDisposable介面的Dispose方法。由於c#要求公用Dispose方法還必須是IDisposable的Dispose方法的實現,所以會執行和st的Dispose方法一樣的代碼,並輸出“Dispose”。我們將上面的代碼改進如下:
internal static class ExplicitInterfaceMethodImpl{ public sealed class SimpleType : IDisposable { public void Dispose() { Console.WriteLine("Dispose"); } void IDisposable.Dispose() { Console.WriteLine("IDisposable Dispose"); } } public static void Main() { SimpleType st = new SimpleType(); // This calls the public Dispose method implementation st.Dispose(); // This calls IDisposable's Dispose method implementation IDisposable d = st; d.Dispose(); }}
第一個方法輸出“Dispose”,第二個輸出“IDisposable Dispose”,看起來挺奇怪的。在SimpleType類裡定義的 IDisposable.Dispose()方法,就是一個EIMI,這個EIMI實現不允許指定可訪問性,比如public等,但是編譯器在產生方法的中繼資料時,其可訪問性會被自動設為private,由此我們可以猜測到EIMI不能在衍生類別型中調用。實際上EIMI是極其不好理解的,它有比較多的使用限制,比如實值型別和介面的轉換需要裝箱等等,實際開發中慎用就是了。