c#擴展方法奇思妙用變態篇三:switch/case組擴展

 變態篇二中給出了對if/else、swith/case及while 的擴展,大家評價各不相同,其實本人也感覺有點牽強。其中舉了一個Swith擴展的應用,今天突然有了新想法,對它改進了一些。所謂“語不驚人死不休”,且看這次的改進如何。

我先把擴展的源代碼貼出來,摺疊一下,等看完後面的例子和講解再回來看。(和前面一樣,本文側重想法,代碼演示用,如需使用,請自行完善)


 1   public static class SwithCaseExtension
 2    {
 3        #region SwithCase
 4        public class SwithCase<TCase, TOther>
 5        {
 6            public SwithCase(TCase value, Action<TOther> action)
 7            {
 8                Value = value;
 9                Action = action;
10            }

11            public TCase Value getprivate set; }
12            public Action<TOther> Action getprivate set; }
13        }

14        #endregion

15
16        #region Swith
17        public static SwithCase<TCase, TOther> Switch<TCase, TOther>(this TCase t, Action<TOther> action) where TCase : IEquatable<TCase>
18        {
19            return new SwithCase<TCase, TOther>(t, action);
20        }

21
22        public static SwithCase<TCase, TOther> Switch<TInput, TCase, TOther>(this TInput t, Func<TInput, TCase> selector, Action<TOther> action) where TCase : IEquatable<TCase>
23        {
24            return new SwithCase<TCase, TOther>(selector(t), action);
25        }

26        #endregion

27
28        #region Case
29        public static SwithCase<TCase, TOther> Case<TCase, TOther>(this SwithCase<TCase, TOther> sc, TCase option, TOther other) where TCase : IEquatable<TCase>
30        {
31            return Case(sc, option, other, true);
32        }

33        
34        
35        public static SwithCase<TCase, TOther> Case<TCase, TOther>(this SwithCase<TCase, TOther> sc, TCase option, TOther other, bool bBreak) where TCase : IEquatable<TCase>
36        {
37            return Case(sc, c=>c.Equals(option), other, bBreak);
38        }

39
40
41        public static SwithCase<TCase, TOther> Case<TCase, TOther>(this SwithCase<TCase, TOther> sc, Predicate<TCase> predict, TOther other) where TCase : IEquatable<TCase>
42        {
43            return Case(sc, predict, other, true);
44        }

45
46        public static SwithCase<TCase, TOther> Case<TCase, TOther>(this SwithCase<TCase, TOther> sc, Predicate<TCase> predict, TOther other, bool bBreak) where TCase : IEquatable<TCase>
47        {
48            if (sc == nullreturn null;
49            if (predict(sc.Value))
50            {
51                sc.Action(other);
52                return bBreak ? null : sc;
53            }

54            else return sc;
55        }

56        #endregion

57
58        #region Default
59        public static void Default<TCase, TOther>(this SwithCase<TCase, TOther> sc, TOther other)
60        {
61            if (sc == nullreturn;
62            sc.Action(other);
63        }

64        #endregion

65    }

這段代碼定義了三個擴展Switch、Case和Default。
首先看這些擴展的一個最簡單的應用,如下:

1     string typeName = string.Empty;
2     typeId.Switch((string s) => typeName = s)
3         .Case(0"食品")
4         .Case(1"飲料")
5         .Case(2"酒水")
6         .Case(3"毒藥")
7         .Default("未知");

輸入一個整數,返回它表示的含義。(很多方法可以解決這個問題,此處示例,請勿較真!)
這裏解釋一下,第2行中的lambda表達式:(string s)=>typeName = s 需要傳入一個字符串參數。
第3~6行的Case在滿足條件時將第二個參數“內部”返回,傳給(string s)=>typeName = s。
這樣來理解:當typeId爲0時,Case返回“食品”並傳入給lambda,爲1時返回“飲料”...
最終lambda將值賦給了typeName。
有點繞,一定按這個思路去想,否則下面就不好明白了。

把上面的代碼“展開”,相當於以下代碼(有點長,摺疊起來好一些)


 1     string typeName = string.Empty;
 2     switch (typeId)
 3     {
 4         case 0:
 5             typeName = "食品";
 6             break;
 7         case 1:
 8             typeName = "食品";
 9             break;
10         case 2:
11             typeName = "酒水";
12             break;
13         case 3:
14             typeName = "毒藥";
15             break;
16         default:
17             typeName = "未知";
18             break;
19     }

代碼行數比較一下吧,前面的是7行,這兒是19行。

再來看一個複雜點的應用,用戶註冊時經常要檢驗用戶密碼的強度,通常用不同顏色展示給用戶,讓用戶有個直觀的瞭解。
這裏我們簡化一下,僅判斷密碼的長度,越長認爲安全性越好。用紅的背景色展示給用戶,密碼越不安全越紅,反之則紅色變淡。
通俗點,密碼長紅色淡,密碼短紅色深。
下面根據密碼長度獲取背景顏色:

 1     private static Color GetBackColor(string password)
 2     {
 3         Color backColor = default(Color);
 4         password.Switch(p => p.Length, (Color c) => backColor = c)
 5             .Case(l => l <= 4, Color.FromArgb(25500))
 6             .Case(l => l <= 6, Color.FromArgb(2556363))
 7             .Case(7, Color.FromArgb(255127127))
 8             .Case(8, Color.FromArgb(255191191))
 9             .Default(Color.FromArgb(255255255));
10         return backColor;
11     }

先看Switch的這裏有兩個參數,在第一個位置插入了一個參數,它取了密碼的長度來進行比較。
同理第4行的lambda將,Case返回的顏色賦給backColor。
第5、6行Case中第一個參數是一個lambda,Case擴展即可以與一個實際值比較相等,也可以判斷範圍。

仔細看這段代碼,Color.FromArgbp被調用了很多次,我們來重構一下:

 1     private static Color GetBackColor2(string password)
 2     {
 3         Color backColor = default(Color);
 4         password.Switch(p => p.Length, (int red) => backColor = Color.FromArgb(255256 - red, 256 - red))
 5             .Case(l => l <= 4256)
 6             .Case(l => l <= 6192)
 7             .Case(7128)
 8             .Case(864)
 9             .Default(0);
10         return backColor;
11     }

這樣看起來簡單了吧,當然Color.FromArgb也可以放在return中。這裏是故意放在了Switch的第二個參數中的!

到現在爲止估計大家應該有一個疑問了,原來的switch/case中可以使用“break”直接返回,這裏是怎麼處理的呢?
Case還有第三個參數,它用來處理實是否break,爲true時break,false時繼續下一個Case。
個人感覺大多數情況下,符合某個條件後一般不需要繼續其它的了,所以重載傳入true,即默認break。
與switch/case是相反的。如果不習慣,你可以在擴展的源代碼中修改一下!

我們再看一個非break的情形如何使用,應用場景如下:
      一款關於球的遊戲:
            進球6~10個(包含6、10,以下同),可得獎勵 1;
            進球11~20,再獎勵 10;
            進球21~50,再獎勵 100;
            進球51~100,再獎勵 1000;
            進球超過100,再獎勵 10000;
      例:進球30個,獎勵爲 1+10+100 = 111。
      寫個函數計算獎勵。

 1     private static int GetReward(int count)
 2     {
 3         int score = 0;
 4         count.Switch((int i) => score += i)
 5             .Case(c => c > 51false)
 6             .Case(c => c > 1010false)
 7             .Case(c => c > 20100false)
 8             .Case(c => c > 501000false)
 9             .Case(c => c > 10010000false);
10         return score;
11     }

Case的最後一個參數false表示符合條件後不break,繼續下一個Case。

多個Case鏈起來使用不一定最後一個參數全爲false,可以如下調用:

1     int i = 5;
2     int j = 0;
3     i.Switch((int k) => j += i * i)
4         .Case(11true)
5         .Case(22false)
6         .Case(31)
7         .Case(42true)
8         .Default(5);

這段代碼沒實際意思,只是爲了說明可以像swith/case那樣使用。

這裏也使用了鏈式編程”(概念參見我的相關文章),它們是如何串起來的。我們看下這幾個擴展的定義:

1     public static SwithCase<TCase, TOther> Switch<TInput, TCase, TOther>
2         (this TInput t, Func<TInput, TCase> selector, Action<TOther> action)
3             where TCase : IEquatable<TCase> {}
4 
5     public static SwithCase<TCase, TOther> Case<TCase, TOther>
6         (this SwithCase<TCase, TOther> sc, Predicate<TCase> predict, TOther other, bool bBreak)
7             where TCase : IEquatable<TCase> {}
8 
9     public static void Default<TCase, TOther>(this SwithCase<TCase, TOther> sc, TOther other){}

Swith擴展可用於任意類型,任意類型的實例都可以調用Swith。
Swith擴展返回SwithCase<TCase, TOther>,這是一簡單的泛型類,如下:
因爲Case需要“判斷的值”和“判斷成功的處理”,也就說需要兩個“值”(確切說是一個值和一個委託)。
我們使用SwithCase泛型類把這兩“值”封裝起來,作爲Switch的返回。
Case和Default是對SwithCase
泛型類作的擴展,Case的返回值也是SwithCase泛型類。
通過SwitchCase泛型類,我們就串起來了,以Swith開始,以Default結束(Default沒返回值,Default也可以省略)。

SwichCase泛型類用作這裏鏈式編程的鏈接有以下好處。
      1.平時Case和Default擴展是隱藏的,不會出現在代碼智能提示中(因爲它是對SwitchCase作的擴展)。
      2.只有在Switch擴展的智能提示中,纔有Case和Default,纔可以使用。
基於以上特性,我稱之爲“組擴展” :它們是一組,只有使用了組長(Switch)後,才能使用組員。

“組擴展”的優勢在於對其它代碼的“污染”小(只有一個顯示在智能提示中),也避免了直接調用組成員的非法操作。

組擴展先說到這裏,我們再來看下Swith的兩個參數:Func<TInput, TCase> selector和Action<TOther> action。
第一參數可對傳入的實例進行一些處理(調用屬性、方法或返回一個新的對象),第二個參數是一個Action<T>可以封閉複雜的操作。
有了這兩個參數,Swith可以非常靈活。遠不只以上幾個應用,只是現在還沒發掘出來。
大刀可以殺敵人,也可以削蘋果,關鍵在誰手中,怎麼用。

順便提一下,Switch擴展還可以再加入新的參數,來消減GetReward中的多個“c => c >”,前面的源碼中沒有給出實現,感興趣可自己嘗試一下。

有一點說明一下,第一個例子中Case(1, "飲料"),默認爲typeId爲1時自動break,但是在鏈式編程中是無法忽略後面的Case直接返回的。
現在的處理是判斷成功後,返回null值給下一個Case, 下一個Case什麼也不執行再向下傳遞null值...直到最後一個。
這樣處理會帶來一定的性能損失(很小很小),這是鏈式編程的缺點,無法解決。

這個方法思路上有點怪,性能也有損失,但它確實能減少代碼量(是否易於書寫和維護另說)。
每個人想法不同,思路相差很大,不知道這裏的擴展是否適合你。也不知道你能否接收“組擴展”的概念。

 1     public class SwithCase<TCase, TOther>
 2     {
 3         public SwithCase(TCase value, Action<TOther> action)
 4         {
 5             Value = value;
 6             Action = action;
 7         }
 8         public TCase Value { getprivate set; }
 9         public Action<TOther> Action { getprivate set; }
10     }

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章