跳到主要內容

C# 委派(Delegate) (一)


今天要介紹的是C#的委派 Delegate


Delegate的發展由函數指標(funtion pointer)而來,可以說Delegate是函數指標中的語法糖(Syntactic sugar)也不為過,在Java中則是Sam類。早期在C# 1.0的時候,匿名函數與Lambda表達式還沒有被發展出來,C與C++有函數指標來負責打包函數(function),那C#呢?我們都知道物件導向程式OOP中所有的東西都該是物件,所以C#就創造了一個類別專門來打包函數。


本篇開始會依序從C#的發展順序介紹委派

Delegate、Anonymous Function、Lambda Expression、Func<>、Action<>

委派含括很大的範圍,故可能會花上幾篇的篇幅來一一介紹。



  1.Delegate



首先來介紹的是最初的委派(Delegate)。月亮是外國的圓,我們先來看看微軟(MicroSoft)怎麼介紹自家的的委派--A delegate is a reference type that can be used to encapsulate a named or an anonymous method--一種用來封裝具名或匿名方法的參考類型。至於為什麼封裝函數叫做委派呢,不如試想,原本有一個流程方法(Sop)是要由A公司去完成的,但我們將他外包給委派給B公司去做,把方法流程外包的行為就叫做委派,這樣不難理解吧。

廢話不多說,立刻來看委派是如何被宣告(declare)以及使用的。

委派的宣告:
    public delegate int MyDelegete(int x); 

我們可以注意到一個委派類型的宣告簽章(Signatures),就像是類似宣告一個方法般,給他回傳型態、名稱、參數就完成宣告了,所以我們先做的是宣告一個回傳型態為int、委派類型名稱為Mydelegate、參數型態為int的委派宣告。好啦,宣告完成拉接下來是封裝方法。既然委派是一種類型,所以我們必須將他實作(implement)。
挖哩怎麼報紅了,原來委派類型變數的宣告必須在建構子參數中給與這個符合此委派類型簽章的方法,於是我們必須相應而生出一個回傳型態、參數型態皆為int的方法來完成委派變數的宣告。


        public delegate int MyDelegete(int x);        
        static void Main(string[] args)
        {
            MyDelegete myDelegete = new MyDelegete(AddOne);
        }
        public static int AddOne(int x)
        {
            return  ++x;
        }
我們對於委派變數的宣告終於成功了,馬上來使用他吧


        static void Main(string[] args)
        {
            int i = 1;
            MyDelegete myDelegete = new MyDelegete(AddOne);
            //MyDelegete myDelegete = AddOne; //或是在C# 2.0 中可以這樣宣告
            i = myDelegete(i);
            Console.WriteLine(i); //Output 2
            Console.ReadKey();
        }

好吧,事情發展至此,但或許我們這時候產生了一個疑惑,這樣的用法,委派看起來是不是跟方法沒什麼差別呢,那我們幹嘛使用委派呢,我們必須再重新思考為何使用委派,為何要把方法封裝進變數內。


假如今天有一個十個流程(方法、函數),這十個流程如果大部分都在做一樣的事,但其中只有小部分的不同呢?是不是就可以把這小部分不同邏輯的地方抽出來呢?

我們可以發現--再相似的流程中把不同的邏輯抽出來就是委派的精隨。
        static void Main(string[] args)
        {
            var array = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
            Console.WriteLine(string.Join(",", Change1(array)));
            Console.WriteLine(string.Join(",", Change2(array)));
            Console.WriteLine(string.Join(",", Change3(array)));
            Console.ReadKey();
            //2,3,4,5,6,7,8,9,10
            //2,4,6,8,10,12,14,16,18
            //1,4,9,16,25,36,49,64,81

        }

        public static int[] Change1(int[] _array)
        {
            var array = new int[_array.Length];
            for (int i = 0, c = _array.Length; i < c; i++)
            {
                array[i] = _array[i] + 1;
            }
            return array;
        }
        public static int[] Change2(int[] _array)
        {
            var array = new int[_array.Length];
            for (int i = 0, c = _array.Length; i < c; i++)
            {
                array[i] = _array[i] * 2;
            }
            return array;
        }
        public static int[] Change3(int[] _array)
        {
            var array = new int[_array.Length];
            for (int i = 0, c = _array.Length; i < c; i++)
            {
                array[i] = _array[i] * _array[i];
            }
            return array;
        }
觀察以上的方法,發現這三個方法大部分的邏輯都很像,唯獨內部對Array各個元素處理的方式不一樣,試著用新學到的委派來抽出不同的邏輯吧。


        static void Main(string[] args)
        {
            var array = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
            MyDelegete myDelegete1 = new MyDelegete(AddOne);
            MyDelegete myDelegete2 = new MyDelegete(MultipleTwo);
            MyDelegete myDelegete3 = new MyDelegete(Square);
            Console.WriteLine(string.Join(",", Change(array, myDelegete1)));
            Console.WriteLine(string.Join(",", Change(array, myDelegete2)));
            Console.WriteLine(string.Join(",", Change(array, myDelegete3)));
            Console.ReadKey();
            //2,3,4,5,6,7,8,9,10
            //2,4,6,8,10,12,14,16,18
            //1,4,9,16,25,36,49,64,81
        }
        public static int AddOne(int number)
        {
            return ++number;
        }
        public static int MultipleTwo(int number)
        {
            return number*2;
        }
        public static int Square(int number)
        {
            return number * number;
        }
        public static int[] Change(int[] _array, MyDelegete myDelegete)
        {
            var array = new int[_array.Length];
            for (int i = 0, c = _array.Length; i < c; i++)
            {
                array[i] = myDelegete(_array[i]);
            }
            return array;
        }
恩,我們可能會疑惑,看起來沒減少很多程式碼阿,但又隨即可以發現,假如Change方法是一百行呢? 三遍不就是三百行了呢?又或是有一個流程已經先設定好大部分的邏輯,而有小部分希望使用者自訂義邏輯使的程式使用更彈性呢?
事實上,委派能做的事遠遠不只如此,應用很多,但核心理念是不變的--就是我們將方法邏輯外包封裝至委派變數中,要怎麼使用端看大家囉。


最後做個重點總結:


  1. 委派(delegate)是一種參考類型(reference type),負責具名或匿名方法的封裝。
  2. 委派的宣告類似於方法(function),只有與委派相同簽章(Signatures)的方法能被封裝。
  3. 委派物件變數宣告的同時才將方法封裝,而變數就是被封裝的方法。





留言

張貼留言

這個網誌中的熱門文章

C# 委派(Delegate) (二) - Anonymous Function、Lambda Expression

上一篇中 C# 委派(Delegate) (一) 介紹了Delegate類基本觀念、宣告方式與使用方式,本篇將照著C#中委派的發展史中,繼續介紹 Anonymous Function、Lambda Expression。 Anonymous Function 還記得上篇的範例中,我們每宣告一個委派( delegate )的變數( variable )就要寫一個跟委派相同簽章的 具名 方法對應是吧? 但是這個專用來給委派變數宣告用 具名 方法, 具名 不是顯得很多餘嗎,我們似乎不太需要知道這個方法叫什麼名字啊。 public delegate int MyDelegate(int x); static void Main(string[] args) { //AddOne是專門寫來給委派變數使用的方法,這個具名方法只用一次,從此以後AddOne沒在被用過。 MyDelegate mydelegate = new MyDelegate(AddOne); } public static int AddOne(int number) { return ++number; } 所以在C#2.0中貼心的微軟發展了一套專門給委派變數宣告專用的語法糖( Syntactic sugar )叫做匿名方法( Anonymous Function )。 public delegate int MyDelegate(int x); static void Main(string[] args) { //不需要再寫一個AddOne方法 MyDelegate mydelegate = delegate (int number) { return number + 1; }; } 所以在C#2.0中只要如上宣告就完成了委派變數的宣告。匿名方法的變數宣告中,左式必須要使用明確的委派類別來宣告(這邊使用 MyDelegate ),不能使用 var 關鍵字,如果堅持要使用var關

Reference Type & Value Type

相信有仔細看過前一篇文章後 對於C#中Value Type 與Ref  Type有了一些初步的了解(才怪) 今天直接 Passing By Value 與 Passing By Reference來探討Value Type 與Ref  Type的差別 話不多說舉個例子 1.參考型別(Reference Type) 假設某個學校出了一個模範生小明,老師希望以後所有的學生都像小明一樣聰明 所以把小明克隆了一個新的取名叫做小華 看以下程式碼 public static void Main() { var student1 = new Stutent(); student1.Name = "阿明"; var student2 = student1; student2.Name = "小華"; Console.WriteLine(student1.Name); } class Stutent { public string Name{get;set;} } 猜猜Output出什麼呢 答案是小華 什麼? 為什麼? 靠北不是把令了一個新的變數student2來裝student1了嘛 那為什麼把新生出來的student2的名字取為小華 導致原本的小明也變成小華了呢 老師感到疑惑,小明到底跑哪去了呢,聰明的老師立刻打開電腦 Google了Msdn才發現   阿~!  小華原來就是小明 還裝?! 因為  Class Student是一個 參考型別 參考型別的變數 承載的是記憶體的位置 將一個新的座位,指派給小明, 並把新座位的主人改名為小華, 然後在請原本小明座位上的人大聲喊出自己的名字 小明 當然就會說 "我是小華" 囉 仔細看看這段程式碼 var student1 = new Stutent(); 再把它中文翻譯 我宣告了一個變數student1 並指派為一個新的學生 口語邏輯上