[C#] 利用Dictionary集合和委派完整移除if else的分支判斷
先從簡單的開始講
原本的if else寫法:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
namespace SilverlightApplication3
{
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
}
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
string userInput = "櫻桃";//使用者輸入的文字
if (userInput == "蘋果")
{
showApple();
}
else if (userInput == "香蕉")
{
showBanana();
}
else if (userInput == "櫻桃")
{
showCherry();
}
}
private void showApple()
{
//Do Apple something...
MessageBox.Show("Apple");
}
private void showBanana()
{
//Do Banana something...
MessageBox.Show("Banana");
}
private void showCherry()
{
//Do Cherry something...
MessageBox.Show("Cherry");
}
}
}
雖然先前提過[C#] 用反射(映射)移除if…else陳述式
可以用反射來移除if分支,但要是遇到輸入的字串是中文字,程式碼就得跟著使用中文命名,感覺怪怪的↓…
string userInput = "櫻桃";//使用者輸入的文字
Type t = Type.GetType("SilverlightApplication3.MyUserControl");
//拼接要執行method的名稱(中文)
string methodName = "嘻嘻哈哈";
object result = t.InvokeMember(methodName, BindingFlags.InvokeMethod, null, Activator.CreateInstance(t), null);
條件是字串的判斷
今天剛好遇到要寫很多分支條件判斷,發現如果只是單純的中文字串,應該可以把 條件和要做的Method 都先放入Dictionary中
再依據使用者輸入的字串,直接挑出目標Method來執行就好
所以上述程式碼可以改良(具名委派版)↓
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
}
public delegate void DoSomething();//宣告委派
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
string userInput = "櫻桃";//使用者輸入的文字
//把原本if要判斷的條件字串儲存在Key,由於原本的if條件本來就不會重覆,正適合當Key
//各個Key(條件)要執行的Method就儲存在Value
Dictionary<string, DoSomething> d = new Dictionary<string, DoSomething>()
{
{"蘋果", new DoSomething(showApple)},
{"香蕉", new DoSomething(showBanana)},
{"櫻桃", new DoSomething(showCherry)}};
d[userInput].Invoke();//直接挑選要執行的Method來執行
}
private void showApple()
{
//Do Apple something...
MessageBox.Show("Apple");
}
private void showBanana()
{
//Do Banana something...
MessageBox.Show("Banana");
}
private void showCherry()
{
//Do Cherry something...
MessageBox.Show("Cherry");
}
}
如果懶得宣告具名委派,寫得更簡潔的話↓
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
}
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
string userInput = "櫻桃";//使用者輸入的文字
Dictionary<string, Action> d = new Dictionary<string, Action>() {
{"蘋果", ()=>{ MessageBox.Show("Apple"); }}, //各條件要做的事....
{"香蕉", ()=>{ MessageBox.Show("Banana"); }},
{"櫻桃", ()=>{ MessageBox.Show("Cherry"); }}
};
d[userInput].Invoke();//直接挑選要執行的Method來執行
}
}
↑如此一來,各條件要做的事都很整齊地列出來,Code也變得比較乾淨。
條件是數字大小的判斷
但「if 也有判斷數字區間的情況,該怎麼辦呢?」
例如以下:
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
}
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
int userInput = 14;//使用者輸入的文字
if (userInput >= 1 && userInput <= 10)//在1到10的區間
{
showApple();
}
else if (userInput >= 11 && userInput <= 20)//在11到20的區間
{
showBanana();
}
else if (userInput >= 21 && userInput <= 30)//在21到30的區間
{
showCherry();
}
}
private void showApple()
{
//Do Apple something...
MessageBox.Show("Apple");
}
private void showBanana()
{
//Do Banana something...
MessageBox.Show("Banana");
}
private void showCherry()
{
//Do Cherry something...
MessageBox.Show("Cherry");
}
}
基本上也可以用Dictionary,也是把條件放在Key的位置,要做的事放在Value
條件式會回傳true Or false,所以就可以利用Linq挑選出Key回傳值為true的Value(Method)來執行即可
如下:
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
}
public delegate bool MyKey(int userInput);
public delegate void MyValue();
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
int userInput = 14;//使用者輸入的文字
Dictionary<MyKey, MyValue> d = new Dictionary<MyKey, MyValue>()
{
{new MyKey(condition1), new MyValue(showApple) },//各條件要做的事....
{new MyKey(condition2), new MyValue(showBanana)},
{new MyKey(condition3), new MyValue(showCherry)},
};
//挑選出Key的回傳值為true的Method來執行
d.Where(x => x.Key(userInput) == true).FirstOrDefault().Value.Invoke();
}
//把條件抽出成Method
private bool condition1(int i)
{
return (i>=1 && i<=10);
}
private bool condition2(int i)
{
return (i >= 11 && i <= 20);
}
private bool condition3(int i)
{
return (i >= 21 && i <= 30);
}
private void showApple()
{
//Do Apple something...
MessageBox.Show("Apple");
}
private void showBanana()
{
//Do Banana something...
MessageBox.Show("Banana");
}
private void showCherry()
{
//Do Cherry something...
MessageBox.Show("Cherry");
}
}
簡潔寫法:
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
int userInput = 14;//使用者輸入的文字
Dictionary<Func<int, bool>, Action> d = new Dictionary<Func<int, bool>, Action>() {
{ (a)=>{return (a >= 1 && a <= 10);}, ()=>{ MessageBox.Show("Apple"); }}, //各條件要做的事....
{ (a)=>{return (a >= 11 && a <= 20);}, ()=>{ MessageBox.Show("Banana"); }},
{ (a)=>{return (a >= 21 && a <= 30);}, ()=>{ MessageBox.Show("Cherry"); }}
};
//挑選出Key回傳值為true的Method來執行
d.Where(x => x.Key(userInput) == true).FirstOrDefault().Value.Invoke();
}
多個條件符合的判斷
看完以上兩個例子,再來假設一點
如果有符合多個條件的情況,如下:
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
}
//這次把else移掉,每個if都會進行判斷,並且有兩個條件符合
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
int userInput = 14;//使用者輸入的文字
if(userInput>0)
{
showPlus();
}
if (userInput >= 1 && userInput <= 10)//在1到10的區間
{
showApple();
}
if (userInput >= 11 && userInput <= 20)//在11到20的區間
{
showBanana();
}
if (userInput >= 21 && userInput <= 30)//在21到30的區間
{
showCherry();
}
}
private void showPlus()
{
//Do plus something...
MessageBox.Show("Plus");
}
private void showApple()
{
//Do Apple something...
MessageBox.Show("Apple");
}
private void showBanana()
{
//Do Banana something...
MessageBox.Show("Banana");
}
private void showCherry()
{
//Do Cherry something...
MessageBox.Show("Cherry");
}
}
也是可以改寫成如下:
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
}
public delegate bool MyKey(int userInput);
public delegate void MyValue();
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
int userInput = 14;//使用者輸入的文字
Dictionary<MyKey, MyValue> d = new Dictionary<MyKey, MyValue>()
{
{new MyKey(isPlus), new MyValue(showPlus)},
{new MyKey(condition1), new MyValue(showApple) },//各條件要做的事....
{new MyKey(condition2), new MyValue(showBanana)},
{new MyKey(condition3), new MyValue(showCherry)},
};
//※只是把原本的.FirstOrDefault(),改成foreach走訪每筆物件,並在迴圈中執行Method即可
//挑選出Key回傳值為true的Method來執行,並注意集合內的物件順序就是Method執行順序
foreach (KeyValuePair<MyKey,MyValue> item in d.Where(x => x.Key(userInput) == true))
{
item.Value.Invoke();
}
}
//把條件抽出成Method
private bool isPlus(int i)
{
return i > 0;
}
private bool condition1(int i)
{
return (i >= 1 && i <= 10);
}
private bool condition2(int i)
{
return (i >= 11 && i <= 20);
}
private bool condition3(int i)
{
return (i >= 21 && i <= 30);
}
private void showPlus()
{
//Do plus something...
MessageBox.Show("Plus");
}
private void showApple()
{
//Do Apple something...
MessageBox.Show("Apple");
}
private void showBanana()
{
//Do Banana something...
MessageBox.Show("Banana");
}
private void showCherry()
{
//Do Cherry something...
MessageBox.Show("Cherry");
}
}
簡潔寫法:
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
}
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
int userInput = 14;//使用者輸入的文字
Dictionary<Func<int, bool>, Action> d = new Dictionary<Func<int, bool>, Action>() {
{(a)=>{ return (a > 0);} , ()=>{ MessageBox.Show("Plus"); }},
{ (a)=>{return (a >= 1 && a <= 10);}, ()=>{ MessageBox.Show("Apple"); }}, //各條件要做的事....
{ (a)=>{return (a >= 11 && a <= 20);}, ()=>{ MessageBox.Show("Banana"); }},
{ (a)=>{return (a >= 21 && a <= 30);}, ()=>{ MessageBox.Show("Cherry"); }}
};
//※只是把原本的.FirstOrDefault(),改成foreach走訪每筆物件,並在迴圈中執行Method即可
//挑選出Key回傳值為true的Method來執行
foreach (KeyValuePair<Func<int, bool>, Action> item in d.Where(x => x.Key(userInput) == true))
{
item.Value.Invoke();
}
}
}
複合式條件的判斷
如果原本的if else要判斷的條件很複雜,如下:
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
}
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
//使用者輸入的兩個訊息
int numUser = 12;
string strUser = "你好";
if (numUser>0 && strUser.Length==2)//複合式條件
{
showFirstMsg();
}
//↑↓這兩個if都會執行
if (numUser==12 || strUser.StartsWith("你"))
{
showSecondMsg();
}
else if(numUser>=0&& numUser<=10 && strUser=="測試")
{
showThirdMsg();
}
}
private void showFirstMsg()
{
MessageBox.Show("數字大於0,字串長度等於2");
}
private void showSecondMsg()
{
MessageBox.Show("數字等於12,字串開頭為'你'");
}
private void showThirdMsg()
{
MessageBox.Show("數字介於0與10之間,字串等於'測試'");
}
}
在不使用Design Pattern下
也是可以根據以上技巧來改寫,只是我覺得再進階下去,反而寫if-else還比較清爽XD
可以把條件放入Dictionary的Key,由於這次的條件要比較兩個變數,所以輸入參數有兩個(int和string),條件的回傳值仍為true Or false(boolean)
Dictionary的Value同樣地放入要執行的Method
範例↓:
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
}
//宣告委派
public delegate bool MyKey(int numUser, string strUser);
public delegate void MyValue();
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
//使用者輸入的兩個訊息
int numUser = 12;
string strUser = "你好";
Dictionary<MyKey, MyValue> d = new Dictionary<MyKey, MyValue>()
{
{new MyKey(isFirst),new MyValue(showFirstMsg)},//各條件要做的事...
{new MyKey(isSecond),new MyValue(showSecondMsg)},
{new MyKey(isThird),new MyValue(showThirdMsg)}
};
//由於可能有多個條件符合,要跑foreach,執行符合的Method
foreach (KeyValuePair<MyKey,MyValue> item in d.Where(x=>x.Key(numUser,strUser)==true))
{
item.Value.Invoke();
}
}
//把條件抽出成Method
private bool isFirst(int numUser,string strUser)
{
return (numUser > 0 && strUser.Length == 2);
}
private bool isSecond(int numUser, string strUser)
{
return (numUser == 12 || strUser.StartsWith("你"));
}
private bool isThird(int numUser, string strUser)
{
return (numUser >= 0 && numUser <= 10 && strUser == "測試");
}
private void showFirstMsg()
{
MessageBox.Show("數字大於0,字串長度等於2");
}
private void showSecondMsg()
{
MessageBox.Show("數字等於12,字串開頭為'你'");
}
private void showThirdMsg()
{
MessageBox.Show("數字介於0與10之間,字串等於'測試'");
}
}
簡潔寫法↓
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
}
//複合條件的簡潔寫法看起來反而就比較雜亂了...
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
//使用者輸入的兩個訊息
int numUser = 12;
string strUser = "你好";
Dictionary<Func<int, string,bool>, Action> d = new Dictionary<Func<int, string,bool>, Action>()
{
{(numUser1,strUser1)=>{return (numUser1 > 0 && strUser1.Length == 2);},()=>{ MessageBox.Show("數字大於0,字串長度等於2");}},//各條件要做的事...
{(numUser1,strUser1)=>{return (numUser == 12 || strUser.StartsWith("你"));},()=>{MessageBox.Show("數字等於12,字串開頭為'你'");}},
{(numUser1,strUser1)=>{return (numUser >= 0 && numUser <= 10 && strUser == "測試");},()=>{ MessageBox.Show("數字介於0與10之間,字串等於'測試'");} }
};
//由於可能有多個條件符合,要跑foreach,執行符合的Method
foreach (KeyValuePair<Func<int,string,bool>,Action> item in d.Where(x=>x.Key(numUser,strUser)==true))
{
item.Value.Invoke();
}
}
}
結語
以上是沒有使用Design Pattern沒有使用物件導向來移除if else分支做法
雖說看起來完美移除if分支,不過還是有個缺點
通常if-else或swith不是會有else(default)都不符合條件的情況嗎
目前處理都不符合的條件下,我想到比較簡潔的寫法,還是得乖乖地寫if敘述當作防呆
例如字串條件的話:
string userInput = "櫻桃";//使用者輸入的文字
Dictionary<string, Action> d = new Dictionary<string, Action>() {
{"蘋果", ()=>{ MessageBox.Show("Apple"); }}, //各條件要做的事....
{"香蕉", ()=>{ MessageBox.Show("Banana"); }},
{"櫻桃", ()=>{ MessageBox.Show("Cherry"); }}
};
if (d.Keys.Contains(userInput))//防呆
{
d[userInput].Invoke();//直接挑選要執行的Method來執行
}
有回傳值的數字條件:
int userInput = 14;//使用者輸入的文字
Dictionary<Func<int, bool>, Action> d = new Dictionary<Func<int, bool>, Action>() {
{ (a)=>{return (a >= 1 && a <= 10);}, ()=>{ MessageBox.Show("Apple"); }}, //各條件要做的事....
{ (a)=>{return (a >= 11 && a <= 20);}, ()=>{ MessageBox.Show("Banana"); }},
{ (a)=>{return (a >= 21 && a <= 30);}, ()=>{ MessageBox.Show("Cherry"); }}
};
if (d.Any(x=>x.Key(userInput)==true) )//防呆
{
//挑選出Key回傳值為true的Method來執行
d.Where(x => x.Key(userInput) == true).FirstOrDefault().Value.Invoke();
}
當輸入的值不在集合當中,程式碼還是得為這種情況寫if來防呆都不符合條件的狀況(由於使用者亂輸入文字)
所以可以確定資料是固定的可以考慮斟酌使用Dictionary+委派
至於怎麼連那個防呆的if都移除呢(可能要把所有的判斷條件都寫在Dictionary裡了),等哪天想到更好解法再補完文章吧
2012.9.27 凌晨追記
想到該如何把那個防呆的if移除掉,只是方法滿彆扭的
有回傳值的數字條件:
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
int userInput = 10000;//使用者輸入的文字
Dictionary<Func<int, bool>, Action> d = new Dictionary<Func<int, bool>, Action>() {
{ (a)=>{return (a >= 1 && a <= 10);}, ()=>{ MessageBox.Show("Apple"); }}, //各條件要做的事....
{ (a)=>{return (a >= 11 && a <= 20);}, ()=>{ MessageBox.Show("Banana"); }},
{ (a)=>{return (a >= 21 && a <= 30);}, ()=>{ MessageBox.Show("Cherry"); }}
};
//條件都不符合的情況
d.Add(//抓出前三筆條件判斷都是false的話...
(a) => { return d.Take(3).All(x => x.Key(userInput) == false); },
() => { MessageBox.Show("數字都不符合條件"); }
);
//挑選出Key回傳值為true的Method來執行
d.Where(x => x.Key(userInput) == true).FirstOrDefault().Value.Invoke();
}
字串條件:
變得要把Dictionary的Key宣告成Func<string,bool>型別,最後改用Linq挑出Key回傳值為true的Method來執行
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
string userInput = "!@#$";//使用者輸入的文字
Dictionary<Func<string, bool>, Action> d = new Dictionary<Func<string, bool>, Action>() {
{(a)=>{return a.Equals("蘋果");}, ()=>{ MessageBox.Show("Apple"); }}, //各條件要做的事....
{(a)=>{return a.Equals("香蕉");}, ()=>{ MessageBox.Show("Banana"); }},
{(a)=>{return a.Equals("櫻桃");}, ()=>{ MessageBox.Show("Cherry"); }}
};
//條件都不符合的情況
d.Add(//抓出前三筆條件判斷都是false的話...
(a) => { return d.Take(3).All(x => x.Key.Equals(userInput) == false); },
() => { MessageBox.Show("字串都不符合條件"); }
);
//挑選出Key回傳值為true的Method來執行
d.Where(x => x.Key(userInput) == true).FirstOrDefault().Value.Invoke();
}
委派真是太神奇了!,可以永遠跟if else說再見(大誤XD
整篇文章講得很長,硬要挑重點的話,我覺得還是把字串判斷的技巧記下來↓
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
string userInput = "櫻桃";//使用者輸入的文字
Dictionary<string, Action> d = new Dictionary<string, Action>() {
{"蘋果", ()=>{ MessageBox.Show("Apple"); }}, //各條件要做的事....
{"香蕉", ()=>{ MessageBox.Show("Banana"); }},
{"櫻桃", ()=>{ MessageBox.Show("Cherry"); }}
};
d[userInput].Invoke();//直接挑選要執行的Method來執行
}
其他可能乖乖地用if敘述或Design Pattern來撰寫程式碼會比較好維護