[C#] 利用Dictionary集合和委派完整移除if else的分支判斷

[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來撰寫程式碼會比較好維護