天天看點

《随筆十九》——C#中的 “ 委托 、 Lambda 表達式”

目錄

什麼是委托

使用委托的步驟 (239P)

給委托指派 (242P)

使用 + 運算符來組合委托 (243P)

使用 += 運算符為委托添加方法 (243P)

運算符 -=  運算符為委托移除方法 (244P)

調用委托

調用帶傳回值的委托 (246P)

調用帶引用(ref)參數的委托 (247)

匿名方法

使用匿名方法 (249P)

匿名方法的文法

Lambda 表達式 (252P)

什麼是委托

 委托是一種存儲函數引用的類型, 委托的聲明類似于函數,不帶函數體, 使用關鍵字delegate 做前辍。 後面才是 傳回類型、函數名、參數表。  

當我們定義了委托之後, 就可以聲明該委托的變量,接着把這個變量初始化為與委托具有相同傳回類型 和參數清單的函數。之後,我們就可以使用該委托變量調用這些函數,就像該變量是一個函數一樣。

namespace HelloWorld_Console
{
    delegate void MyDel(int value); //聲明委托類型
    class Program
    {
        void PrintLow(int value)
        {
            WriteLine($"PrintLow 函數的值為:{value}");
        }
        void PrintHigh(int value)
        {
            WriteLine($"PrintHigh 函數的值為:{value}");
        }
        static void Main(string[] args)
        {
            Program myProgram = new Program();

            Random rand = new Random(); //建立随機整數生成器對象
            int randomValue = rand.Next(99);

            //初始化委托變量,建立委托對象
            MyDel del = randomValue < 50 ? new MyDel(myProgram.PrintLow) : new MyDel(myProgram.PrintHigh);
            del(randomValue); //調用委托
            ReadKey();
        }
    }
}
           
  • 在建立了委托對象之後,并且初始化委托變量, 隻有在程式運作時才能确定該委托變量執行的到底是哪些函數。
  • 在聲明委托類型時,不需要在類内聲明, 因為它是類型聲明,是以我們一般在類外聲明委托。

使用委托的步驟 (239P)

委托也是一種自定義的類型,可以通過以下步驟使用委托:

  • 聲明一個委托類型。 類似于函數,關鍵字delegate 做前辍,沒有函數體。
  • 使用該委托類型聲明一個委托變量。
  • 建立委托類型的對象,把它指派給委托變量。新的委托對象包括指向某個方法的引用, 這個方法和第一步定義的簽名和傳回類型一緻。
  • 還可以選擇為委托對象增加其它方法。 這些方法必須與第一步定義的委托類型有相同的函數原型。
  • 最後你就可以像調用其他方法一樣調用委托。當您調用委托時,它所包含的每個方法都會被執行。
《随筆十九》——C#中的 “ 委托 、 Lambda 表達式”

使用委托的步驟

可以将委托視為包含某些具有相同簽名和傳回類型的有序方法清單的對象:

《随筆十九》——C#中的 “ 委托 、 Lambda 表達式”
  • 方法的清單稱為調用清單。
  • 委托儲存的方法可以是來自任何類或結構, 隻要它們的函數原型相同。
  • 調用清單中的方法可以是執行個體方法和靜态方法。
  • 當調用委托時,将執行其調用清單中的每個方法。
  • 當使用關鍵字new 為委托配置設定記憶體, 此時建立的委托對象會把該方法放入委托的調用清單。(242頁)

給委托指派 (242P)

 由于委托是引用類型, 我們可以通過給它指派來使該委托變量重新指向一個新的委托對象。舊的委托對象會被垃圾回收器(GC)回收。

namespace HelloWorld_Console
{
    delegate void MyDel(int value); //聲明委托類型
    class Program
    {
        void PrintLow(int value)
        {
            WriteLine($"PrintLow 函數的值為:{value}");
        }
        void PrintHigh(int value)
        {
            WriteLine($"PrintHigh 函數的值為:{value}");
        }
        static void Main(string[] args)
        {
            Program myProgram = new Program();

            MyDel mdel = myProgram.PrintLow; //委托變量首先 存儲PrintLow 函數的引用
            int printValue = 5;
            mdel(printValue); //使用委托

            mdel = myProgram.PrintHigh;  //為委托指派,該委托是一個全新的委托
            mdel(printValue); //使用委托
            ReadKey();
        }
    }
}

輸出結果為:

PrintLow 函數的值為:5
PrintHigh 函數的值為:5
           
《随筆十九》——C#中的 “ 委托 、 Lambda 表達式”

使用 + 運算符來組合委托 (243P)

委托可以使用+ 運算符來 “組合 ”, 操作的結果是建立一個新的委托, 其調用清單連接配接了作為操作數的兩個委托的調用清單的副本。

namespace HelloWorld_Console
{
    delegate void MyDel(int value); //聲明委托類型
    class Program
    {
        void PrintLow(int value)
        {
            WriteLine($"PrintLow 函數的值為:{value}");
        }
        void PrintHigh(int value)
        {
            WriteLine($"PrintHigh 函數的值為:{value}");
        }
        static void Main(string[] args)
        {
            Program myProgram = new Program();

            MyDel delA = myProgram.PrintLow;
            MyDel delB = myProgram.PrintHigh;
            MyDel delC = delA + delB; //組合調用清單,現在delC的調用清單有兩個方法

            Random rand = new Random(); //建立随機整數生成器對象
            int randomValue = rand.Next(99);

            //初始化委托變量,建立委托對象
             delC = randomValue < 50 ? new MyDel(myProgram.PrintLow) : new MyDel(myProgram.PrintHigh);
            delC(randomValue); //調用委托
            ReadKey();
        }
    }
}

           

注意: 委托是恒定的, 委托對象被建立後不能再被改變。 

《随筆十九》——C#中的 “ 委托 、 Lambda 表達式”

使用 += 運算符為委托添加方法 (243P)

為委托添加方法使用 += 運算符。 如下代碼為委托添加了兩個方法, 添加的方法會到調用清單中的底部。 在調用清單中的順序是你依次添加的順序。

namespace HelloWorld_Console
{
    delegate void MyDel(int value); //聲明委托類型
    class Program
    {
        void PrintLow(int value)
        {
            WriteLine($"PrintLow 函數的值為:{value}");
        }
        void PrintHigh(int value)
        {
            WriteLine($"PrintHigh 函數的值為:{value}");
        }
        void Print(int value)
        {
            WriteLine($"Print 函數的值為:{value}");
        }
        static void Main(string[] args)
        {
            Program myProgram = new Program();

            MyDel delVar = myProgram.PrintLow;  // 建立委托變量,并且建立委托對象
            delVar += myProgram.PrintHigh; // 為該委托變量添加方法。現在該委托變量的調用清單有三個方法
            delVar += myProgram.Print;

            Random rand = new Random(); //建立随機整數生成器對象
            int randomValue = rand.Next(99);

            //初始化委托變量,建立委托對象
            delVar = randomValue < 50 ? new MyDel(myProgram.PrintLow) : new MyDel(myProgram.PrintHigh);
            delVar(randomValue); //調用委托
            ReadKey();
        }
    }
}

           

注意:  在使用+= 運算符時, 因為委托是不可變的,是以為委托的調用清單添加了兩個方法後的結果其實是委托變量指向了一個新的委托。

運算符 -=  運算符為委托移除方法 (244P)

 使用運算符 -=  從委托删除方法。與委托增加方法一樣,當删除一個委托方法是,其實是建立了一個新的委托。 新的委托是舊委托的副本, 隻是沒有了已經被删除方法的引用。

namespace HelloWorld_Console
{
    delegate void MyDel(int value); //聲明委托類型
    class Program
    {
        void PrintLow(int value)
        {
            WriteLine($"PrintLow 函數的值為:{value}");
        }
        void PrintHigh(int value)
        {
            WriteLine($"PrintHigh 函數的值為:{value}");
        }
        void Print(int value)
        {
            WriteLine($"Print 函數的值為:{value}");
        }
        static void Main(string[] args)
        {
            Program myProgram = new Program();

            MyDel delVar = myProgram.PrintLow;  // 建立委托變量,并且建立委托對象
            delVar += myProgram.PrintHigh; // 為該委托變量添加方法。現在該委托變量的調用清單有三個方法
            delVar += myProgram.Print;
            delVar-= myProgram.Print;  //為委托删除一個方法, 現在委托變量的調用清單有兩個方法
            Random rand = new Random(); //建立随機整數生成器對象
            int randomValue = rand.Next(99);

            //初始化委托變量,建立委托對象
            delVar = randomValue < 50 ? new MyDel(myProgram.PrintLow) : new MyDel(myProgram.PrintHigh);
            delVar(randomValue); //調用委托
            ReadKey();
        }
    }
}
           

如下是移除委托方法時應注意的有:

  • 如果在調用清單中該委托有多個方法, - = 運算符從清單的底部開始搜尋, 并且移除第一個與委托相比對(指的是原型比對)的方法。
  • 試圖删除不在調用清單中的方法沒有效果。
  • 試圖調用空委托(說白了就是該委托變量沒有初始化為一個委托對象)會抛出異常。  我們可以通過把委托和null 進行比較來判斷委托的調用清單是否為空。  如果調用清單為空, 則委托是null。

調用委托

可以像調用方法一樣簡單地調用委托。 用于調用委托的參數将會用于調用調用清單中的每一個方法(除非有輸出參數)。

namespace HelloWorld_Console
{
    delegate void PrintFunction(); //聲明委托類型
    class Program
    {
        public void Print1()
        {
            WriteLine("調用 Print1 函數.");
        }
        public static void Print2()
        {
            WriteLine("調用 Print2 函數.");
        }
        
        static void Main(string[] args)
        {
            Program myProgram = new Program();

            PrintFunction pf = myProgram.Print1; //執行個體化并且初始化該委托
            //給委托添加3個另外的方法
            pf += Program.Print2;
            pf += myProgram.Print1;
            pf += Print2;
            // 現在,委托含有4個方法

            if (pf != null)
            {
                pf();
            }
            else
                WriteLine("該委托是空的!");
            ReadKey();
        }
    }
}

輸出結果為:
調用 Print1 函數.
調用 Print2 函數.
調用 Print1 函數.
調用 Print2 函數.
           
  • 注意 :如果一個方法在調用清單中出現多次,當委托被調用時, 每次在清單中遇到這個方法時它都會被調用一次。
  • 注意:  如果初始化委托變量的 函數是靜态的,  那麼必須是  “ 類名. 成員函數名 ” 或者 直接在指派運算符後面寫 某個具體的函數名 。 如上面的 函數 Print2();

調用帶傳回值的委托 (246P)

  如果委托有傳回值并且在調用清單中有一個以上的方法,會發生下面的情況:

  • 調用清單中最後一個方法傳回的值就是委托調用傳回的值。那麼輸出的值就是最後一個傳回值的值。
  • 調用清單中所有其它方法的傳回值都會被忽略。
namespace HelloWorld_Console
{
    delegate int MyDel(); //聲明委托類型
    class Program
    {
        int IntValue = 5;
        public int Add2() => IntValue += 2;
        public int Add3() => IntValue += 3;


        static void Main(string[] args)
        {
            Program myProgram = new Program();
            MyDel mDel = myProgram.Add2; //建立并初始化委托
            mDel += myProgram.Add3; // 為委托添加方法
            mDel += myProgram.Add2;
            if (mDel != null)
            {
                WriteLine($"輸出值為:{mDel()}"); //調用委托并傳回值
            }
            else
                WriteLine("該委托是空的!");
            ReadKey();
        }
    }
}

輸出結果為:

輸出值為:12
           
《随筆十九》——C#中的 “ 委托 、 Lambda 表達式”

調用帶引用(ref)參數的委托 (247)

如果委托有引用參數, 形參的值會根據調用清單中的一個或多個方法的傳回值而改變。

意識就是說,在調用委托清單中的下一個方法時, 上一個方法形參的值不管是否改變,都會傳遞給調用清單中下一個方法, 以此類推。

namespace HelloWorld_Console
{
    delegate void MyDel (ref int X); //聲明委托類型
    class Program
    {
     
        public void Print1(ref int x) =>  x += 2;
        public  void Print2(ref int x) => x += 3;


        static void Main(string[] args)
        {
            Program myProgram = new Program();
            MyDel mDel = myProgram.Print1; //建立并初始化委托
            mDel += myProgram.Print2; // 為委托添加方法
            mDel += myProgram.Print1;
            int x = 5;
            mDel(ref x);
            if (mDel!=null)
            {
                WriteLine($"輸出委托最後的傳回值:{x}");
            }
            ReadKey();
        }
    }
}

輸出結果為:

輸出委托最後的傳回值:12
           
《随筆十九》——C#中的 “ 委托 、 Lambda 表達式”

匿名方法

 我們可以使用靜态方法或執行個體方法來初始化委托并且建立委托對象。對于這種情況, 這些方法本身可以被代碼的其他部分顯式調用。不過,這個部分也必須是某個類或結構的成員。

然而,如果方法隻會被使用一次——用來初始化委托會怎麼樣呢?在這種情況下,除了建立委托的文法需要,沒有必要建立獨立的具名方法。匿名方法允許我們避免使用獨立的具名方法。

  • 說明 : 匿名方法是在初始化委托時内聯( inline )聲明的方法。

使用匿名方法 (249P)

我們可以在如下地方使用匿名方法:

  • 聲明委托變量時作為初始化表達式
  • 在組合委托時位于指派語句的右側。
  • 為委托添加事件時在指派語句的右邊。

匿名方法的文法

使用匿名方法的文法為:

delete (Paramters){ // Implementation code}
           
  • 其中 Paramters 是參數清單, 如果沒有任何想要使用的參數,可以省略。
  •  { } 裡是語句塊, 它包含了匿名方法的代碼。

下面的代碼顯式初始化委托:

namespace HelloWorld_Console
{
    delegate int MyDel ( int X); //聲明委托類型
    class Program
    {
     
        public  int Print1(int x) => x+20 ;
        static void Main(string[] args)
        {
            Program myProgram = new Program();
            MyDel mDel = myProgram. Print1; //建立并初始化委托
            WriteLine($"輸出值為:{mDel(5)}");
            ReadKey();
        }
    }
}

輸出值為:25
           

下面的代碼隐式初始化委托:

namespace HelloWorld_Console
{
    delegate int MyDel ( int X); //聲明委托類型, 該類型傳回一個int
    class Program
    {
        //public  int Print1(int x) => x+20 ;
        static void Main(string[] args)
        {
            Program myProgram = new Program();
            MyDel mDel = delegate (int x)
            {
                return x + 20;  // 匿名方法的實作代碼本身的傳回值類型 必須跟委托的傳回類型相同,它們都是int
            };
            WriteLine($"輸出值為:{mDel(20)}");
            ReadKey();
        }
    }
}

輸出結果為: 40
           
《随筆十九》——C#中的 “ 委托 、 Lambda 表達式”
  • 注意:  匿名方法不需要顯式地聲明傳回類型, 然而, 匿名方法的實作代碼本身的 傳回值類型 必須跟委托的傳回類型 相同。 
  • 比如:如果委托是void 類型的傳回值, 匿名方法就沒有傳回值。
注意: 除了數組參數,匿名方法的參數清單必須跟委托的 : 參數數量、參數類型、順序、修飾符都要一樣。 說白了就是它們的原型要一樣。

注意:  可以使用 圓括号為空 或者 省略圓括号來 簡化 匿名方法的參數清單,  但是必須滿足以下兩個條件:

  • 委托的參數清單不包含任何 out 參數。
  • 匿名方法不使用任何參數。

注意:  如果委托聲明的參數清單包含了  params  參數, 那麼匿名方法的參數清單 将忽略 params 關鍵字的前辍。

《随筆十九》——C#中的 “ 委托 、 Lambda 表達式”

關于 params 關鍵字的 詳細内容,點選 這裡  檢視。

Lambda 表達式 (252P)

  我們可以使用下列步驟來把 匿名方法 轉換為  Lambda 表達式:

  • 首先删除 delegate 關鍵字
  • 在形參清單和匿名方法的函數體之間方 Lambda 運算符 => 

如下代碼示範了這種轉換。第一行示範了将匿名方法指派給變量mDel。第二行示範了同樣的 ,匿名方法在被轉換成Lambda表達式之後,指派給了變量del:

MyDel mDel = delegate (int x) { return x + 20; };  // 匿名方法
 MyDel del = (int x) => { return x + 20; };  // Lambda 表達式
           

  不過我們還可以更簡潔,編譯器可以通過推斷從委托的聲明中知道委托形參數類型, 是以 Lambad 表達式允許我們省略委托形參數類型。 如下面代碼中 le2 的指派代碼所示:

記住:

  • 帶有委托形參數類型的清單稱為顯式類型
  • 省略委托形參數類型的清單稱為隐式類型
MyDel de1 = delegate (int x) { return x + 1; };  // 匿名方法
            // 都是 Lambda 表達式
            MyDel le1 = (int x) => { return x + 1; }; // 顯式類型
            MyDel le2= (x) => { return x + 1; };  // 隐式類型
            MyDel le3= x => { return x + 1; };
            MyDel le4 = x =>   x + 1;
           
  • 注意 : 如果圓括号中隻有一個隐式類型參數, 我們可以省略周圍的圓括号, 如 le3 的指派代碼所示。
  • 注意:  最後,lambda表達式允許表達式的主體可以是語句塊,也可以是表達式。如果語句塊隻包含一個傳回語句,則可以使用return關鍵字後面的表達式來替換語句塊,如le4的指派所示。
namespace HelloWorld_Console
{
    delegate int MyDel(int par); //聲明委托類型
    class Program
    {

        static void Main(string[] args)
        {
            MyDel de1 = delegate (int x) { return x + 1; };  // 匿名方法

            // 都是 Lambda 表達式
            MyDel le1 = (int x) => { return x + 1; };
            MyDel le2 = (x) => { return x + 1; };
            MyDel le3 = x => { return x + 1; };
            MyDel le4 = x => x + 1;

            WriteLine($"分别輸出值為:{de1(20)},{le1(20)},{le2(20)}" +
                $",{le3(20)},{le4(20)}");
            ReadKey();
        }
    }
}
輸出結果為:

分别輸出值為:21,21,21,21,21
           

  有關Lambda 表達式的參數清單的要點如下:

  • Lambda 表達式參數清單中的參數必須在 參數個數、類型、順序與委托比對。
  • 表達式的參數清單中的參數不一定需要 包含類型(此時是隐式類型),  如果委托有 ref 或out 參數——此時必須注明顯式類型.
  • 如果隻有一個參數, 并且是隐式類型的, 周圍的圓括号可以被省略, 否則必須有括号。
  • 如果沒有參數, 必須使用一組空的圓括号。
《随筆十九》——C#中的 “ 委托 、 Lambda 表達式”