變態篇二中給出了對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 { get; private set; }
12 public Action<TOther> Action { get; private 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 == null) return 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 == null) return;
62 sc.Action(other);
63 }
64 #endregion
65 }
這段代碼定義了三個擴展Switch、Case和Default。
首先看這些擴展的一個最簡單的應用,如下:
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行。
再來看一個複雜點的應用,用戶註冊時經常要檢驗用戶密碼的強度,通常用不同顏色展示給用戶,讓用戶有個直觀的瞭解。
這裏我們簡化一下,僅判斷密碼的長度,越長認爲安全性越好。用紅的背景色展示給用戶,密碼越不安全越紅,反之則紅色變淡。
通俗點,密碼長紅色淡,密碼短紅色深。
下面根據密碼長度獲取背景顏色:
2 {
3 Color backColor = default(Color);
4 password.Switch(p => p.Length, (Color c) => backColor = c)
5 .Case(l => l <= 4, Color.FromArgb(255, 0, 0))
6 .Case(l => l <= 6, Color.FromArgb(255, 63, 63))
7 .Case(7, Color.FromArgb(255, 127, 127))
8 .Case(8, Color.FromArgb(255, 191, 191))
9 .Default(Color.FromArgb(255, 255, 255));
10 return backColor;
11 }
先看Switch的這裏有兩個參數,在第一個位置插入了一個參數,它取了密碼的長度來進行比較。
同理第4行的lambda將,Case返回的顏色賦給backColor。
第5、6行Case中第一個參數是一個lambda,Case擴展即可以與一個實際值比較相等,也可以判斷範圍。
仔細看這段代碼,Color.FromArgbp被調用了很多次,我們來重構一下:
2 {
3 Color backColor = default(Color);
4 password.Switch(p => p.Length, (int red) => backColor = Color.FromArgb(255, 256 - red, 256 - red))
5 .Case(l => l <= 4, 256)
6 .Case(l => l <= 6, 192)
7 .Case(7, 128)
8 .Case(8, 64)
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。
寫個函數計算獎勵。
2 {
3 int score = 0;
4 count.Switch((int i) => score += i)
5 .Case(c => c > 5, 1, false)
6 .Case(c => c > 10, 10, false)
7 .Case(c => c > 20, 100, false)
8 .Case(c => c > 50, 1000, false)
9 .Case(c => c > 100, 10000, false);
10 return score;
11 }
Case的最後一個參數false表示符合條件後不break,繼續下一個Case。
多個Case鏈起來使用不一定最後一個參數全爲false,可以如下調用:
2 int j = 0;
3 i.Switch((int k) => j += i * i)
4 .Case(1, 1, true)
5 .Case(2, 2, false)
6 .Case(3, 1)
7 .Case(4, 2, true)
8 .Default(5);
這段代碼沒實際意思,只是爲了說明可以像swith/case那樣使用。
這裏也使用了“鏈式編程”(概念參見我的相關文章),它們是如何串起來的。我們看下這幾個擴展的定義:
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值...直到最後一個。
這樣處理會帶來一定的性能損失(很小很小),這是鏈式編程的缺點,無法解決。
這個方法思路上有點怪,性能也有損失,但它確實能減少代碼量(是否易於書寫和維護另說)。
每個人想法不同,思路相差很大,不知道這裏的擴展是否適合你。也不知道你能否接收“組擴展”的概念。
2 {
3 public SwithCase(TCase value, Action<TOther> action)
4 {
5 Value = value;
6 Action = action;
7 }
8 public TCase Value { get; private set; }
9 public Action<TOther> Action { get; private set; }
10 }