溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

N001-認知C#中的委托

發(fā)布時間:2020-07-31 03:58:50 來源:網(wǎng)絡 閱讀:757 作者:cpx1975 欄目:編程語言

本章包含的如下認知內(nèi)容

    委托概述

    如何聲明委托?

    如何使用委托?

    Action<T>和Func<T>的使用

    多播委托是什么?

    匿名方法

委托概述:

    委托是尋址方法的.Net版本,是類型安全的類,它定義了返回類型和參數(shù)類型。委托類不僅包含對單個方法的引用,也可以包含對多個方法的引用。

    委托是一個特殊類型的對象,其特殊之處在于,我們之前定義的所有對象包含數(shù)據(jù),而委托包含的只是一個或多個方法的地址。

    委托和lambda表達式直接相關,當參數(shù)是委托的時候,我們可以使用lamabda表達式實現(xiàn)委托引用的方法。   

如何聲明委托?

    我們都知道在C#中使用一個類的時候分為兩個階段。首先,需要定義這個類(告訴編譯器這個類由什么字段和方法組成)。然后實例化類的一個對象(靜態(tài)方法除外)。使用委托時,我們也需要經(jīng)過類似以上的兩個步驟。首先必須定義要使用的委托,對于委托,定義它就是告訴編譯器這種類型的委托表示哪種類型的方法。然后,必須創(chuàng)建該委托的一個或多個實例。編譯器在后臺將創(chuàng)建表示該委托的類。

    委托的基本語法如下:

                  delegate void IntMethodInvoker(int x);

    注釋:上述基本語法案例是說定義了一個委托IntMethodInvoker,并且指定了該委托的每個實例都可以包含一個方法(該方法帶有一個int參數(shù))的引用,并且方法返回值是void(空)。理解委托的一個要點是他們的類型安全性非常高。在定義委托時,必須給出它所表示的方法的簽名和返回類型等全部細節(jié)。說白了就是給方法的簽名和返回類型指定名稱。

    假如我們有如下需求:有一個方法需要使用委托,該方法的接收兩個long型參數(shù),并且返回值是一個double類型,那我我們?yōu)樵摲椒ń⒌奈腥缦拢?/p>

                delegate double TwoLongsOp(long  first, long second);

    注釋:其語法類似于方法的定義,但是沒有方法體。定義的前面需要加上關鍵字delegate。因為定義委托基本上是定義一個新類,所以可以在定義類的任何相同地方定義委托。也就是說,可以在另一個類的內(nèi)部定義,也可以在任何類的外部定義,還可以在名稱空間中把委托定義為頂層對象。根據(jù)定義的可見性和委托的作用域,可以在委托的定義上應用任意常用的訪問修飾符:Public、private、protected等。

    實際上,“定義一個委托”是指“定義一個新類”。委托實現(xiàn)為派生自基類System.MulticastDelegate的類,System.MulticastDelegate又派生自基類System.Delegate.C#編譯器能識別這個類,會使用其委托語法,因此我們不需要了解這個類的具體執(zhí)行情況。這是C#與基類共同合作,使編程更易完成的另一個范例。

    定義好委托后,就可以創(chuàng)建它的一個實例,從而用它存儲特定方法的細節(jié)。

    但是,在術語方面有一個問題,類有兩個不同的術語:“類”表示較廣義的定義,“對象”表示類的實例。但委托只有一個術語。在創(chuàng)建委托的實例,所創(chuàng)建的委托的實例仍稱為委托,必須從上下文中確定委托的確切含義。

如何使用委托:

初接觸:

    下面這段代碼說明如何使用委托(簡單應用):

   

using System;    
    namespace Wrox.ProCSharp.Delegates     
    {     
        class Program     
        {     
            private delegate string GetAString();     
            static void Main()     
            {     
                int x = 40;     
                GetAString firstStringMethod = new GetAString(x.ToString);     
                Console.WriteLine("String is {0}", firstStringMethod.Invoke());     
                Console.ReadLine();     
             }     
        }     
    }

    在這段代碼中,實例化了類型為GetString的一個委托,并對它進行初始化,使它引用整型變量x的ToString()方法。在C#中,委托在語法上總是接收一個參數(shù)的構(gòu)造函數(shù),這個參數(shù)就是委托引用的方法。這個方法必須匹配最初定義委托時的簽名。所以在這個示例中,如果不帶x.ToString來初始化變量firstStringMethod變量會報編譯錯誤。注意:因為int.ToString()是一個實例方法(不是靜態(tài)方法),所以需要指定實例(x)和方法名來正確地初始化委托。

     在上述代碼中的firstStringMethod.Invoke()和firstStringMethod()方法是會產(chǎn)生完全相同的效果的。Invoke()是委托類的方法。因為firstStringMethod是委托類型的一個變量,所以C#編譯器會用firstStringMethod.Invoke()代替firstStringMethod()。

     為了減少輸入量,只要需要委托實例,就可以只傳送地址名稱。這就稱為委托推斷。只要編譯器可以把委托實例解析為特定的類型,這個C#特性就是有效的,所以上面的代碼還有另外一種寫法:

   

 using System;    
     namespace Wrox.ProCSharp.Delegates     
     {     
         class Program     
         {     
             private delegate string GetAString();     
             static void Main()     
             {     
                 int x = 40;     
                  GetAString firstStringMethod = x.ToString;     
                 Console.WriteLine("String is {0}", firstStringMethod.Invoke());     
                  Console.ReadLine();     
             }     
         }     
     }

     注意:調(diào)用上述方法名時輸入形式不能為x.ToString()(不要輸入圓括號),也不能把它傳送給委托變量。輸入圓括號調(diào)用一個方法。調(diào)用x.ToString()方法會返回一個不能賦予委托變量的字符串對象。只能把方法的地址賦予委托變量。

動靜態(tài)方法的委托調(diào)用:

    委托的一個特征是它們的類型是安全的,可以確保被調(diào)用的方法簽名是正確的。但是它不關心在什么類型的對象上調(diào)用該方法,甚至不考慮該方法是靜態(tài)方法,還是實例方法。

    首先建立一個動靜態(tài)都包含的Currency.cs類:

   

namespace Wrox.ProCSharp.Delegates    
    {     
        struct Currency     
        {     
            public uint Dollars;     
            public ushort Cents;            public Currency(uint dollars, ushort cents)    
            {     
                this.Dollars = dollars;     
                this.Cents = cents;     
            }
            public override string ToString()    
            {     
                return string.Format("${0}.{1,-2:00}", Dollars, Cents);     
           }
            public static string GetCurrencyUnit()    
            {     
                 return "Dollar";     
           }      
        }     
    }

    然后使用使用GetAString實例:

   

using System;    
    namespace Wrox.ProCSharp.Delegates     
    {     
       class Program     
       {     
            private delegate string GetAString();     
            static void Main()     
            {     
         
                Currency balance = new Currency(34, 50);                //firstStringMethod reference an instance method    
                 GetAString firstStringMethod = balance.ToString;     
               Console.WriteLine("String is{0}", firstStringMethod.Invoke());
                //firstStringMethod reference a static method    
                firstStringMethod = Currency.GetCurrencyUnit;     
                 Console.WriteLine("String is {0}", firstStringMethod.Invoke());     
                Console.ReadLine();     
             }     
       }     
    }

    注釋:上面說明了如何通過委托調(diào)用動靜態(tài)方法,只要每個方法的簽名匹配委托定義即可。

簡單的委托示例

    首先新建一個包含正方形和長方形的計算方法的類MathOperation.cs,該類包含動靜態(tài)兩種方法:

   

namespace Wrox.ProCSharp.Delegates    
    {     
      class MathOperations     
      {     
        public static double MultiplyByTwo(double value)     
        {     
          return value * 2;     
        }       public static double Square(double value)    
       {     
         return value * value;     
       }     
     }
  }

   然后建立委托來調(diào)用這些方法

 

using System;  namespace Wrox.ProCSharp.Delegates    
   {     
      delegate double DoubleOp(double x);     
      class Program     
      {     
          static void Main()     
          {     
              DoubleOp[] operations ={     
                                         MathOperations.MultiplyByTwo,     
                                         MathOperations.Square     
                                    };     
              for (int i = 0; i < operations.Length; i++)     
              {     
                  Console.WriteLine("Using operation[{0}]", i);     
                  ProcessAndDisplayNumber(operations[i], 2.0);     
                  ProcessAndDisplayNumber(operations[i], 7.94);     
                  ProcessAndDisplayNumber(operations[i], 1.414);     
                  Console.WriteLine();     
              }     
              Console.ReadLine();     
          }     
          static void ProcessAndDisplayNumber(DoubleOp action, double value)     
          {     
              double result = action(value);     
              Console.WriteLine("Value is {0},result of operation is {1}", value, result);     
          }
      }    
  }

    運行結(jié)果:

    Using operation[0]   
    Value is 2,result of operation is 4    
    Value is 7.94,result of operation is 15.88    
    Value is 1.414,result of operation is 2.828
    Using operation[1]   
    Value is 2,result of operation is 4    
    Value is 7.94,result of operation is 63.0436    
    Value is 1.414,result of operation is 1.999396

    注釋:在這段代碼中,實例化了一個DoubleOp委托數(shù)組(需要我們了解的是,一旦定義了委托類,基本上就可以實例化它的實例,就像處理一般的類那樣--所以把一些委托的實例放在數(shù)組中是可以的)。該數(shù)組的每個元素都初始化為由MathOperation類實現(xiàn)的不同操作。然后遍歷這個數(shù)組,每個操作應用到三個不同的值上。這就就說了使用委托的一種方式--把方法組合到一個數(shù)組中來使用,這樣就可以在循環(huán)中調(diào)用不同的方法了。

    簡單案例的代碼解析:

    這段代碼的最為關鍵的一行是把每個委托傳遞給ProcessAndDisplayNumber方法,比如:    
        ProcessAndDisplayNumber(operations[i], 2.0);

    其中傳遞了委托名,但不帶任何參數(shù)。其語法是:

       1、operations[i]表示“這個委托”,也可以說成是委托表示的方法。

       2、2.0表示的是一個參數(shù)。

       3、然后調(diào)用 double result = action(value);這個實際上是調(diào)用委托實例封裝的方法。其返回結(jié)果存儲在result中。

Action<T>和Func<T>委托

    除了為每個參數(shù)和返回類型定義一個新委托類型之外,還可以使用Action<T>和Func<T>委托。

    泛型Action<T>委托表示引用一個void返回類型的方法。這個委托類存在不同的變體,可以傳遞至多16種不同的參數(shù)類型。沒有泛型參數(shù)的Action類可調(diào)用沒有參數(shù)的方法。Action<in T>調(diào)用帶一個參數(shù)的方法,Action<int T1, in T2>  調(diào)用帶兩個參數(shù)的方法,以此類推。

   Func<T>委托可以以類似的方法使用。Func<T>允許調(diào)用帶返回類型的方法,并且也定義了不同的變體,也是至多傳遞16個參數(shù)類型和一個返回類型。Func<out TResult>委托類型可以調(diào)用帶返回類型且無參數(shù)的方法,F(xiàn)unc<in T, out TResult>調(diào)用帶一個參數(shù)的方法,以此類推,注意返回類型只能是一個。

   因此利用Func<T>委托可以有如下的調(diào)用方法:

   using System;
   namespace Wrox.ProCSharp.Delegates    
   {     
      class Program     
       {     
          static void Main()     
           {     
              Func<double, double>[] operations ={     
                                          MathOperations.MultiplyByTwo,     
                                          MathOperations.Square     
                                     };     
               for (int i = 0; i < operations.Length; i++)     
               {     
                   Console.WriteLine("Using operation[{0}]", i);     
                   ProcessAndDisplayNumber(operations[i], 2.0);     
                   ProcessAndDisplayNumber(operations[i], 7.94);     
                  ProcessAndDisplayNumber(operations[i], 1.414);     
                   Console.WriteLine();     
                }     
               Console.ReadLine();     
           }     
           static void ProcessAndDisplayNumber(Func<double, double> action, double value)     
           {     
               double result = action(value);     
               Console.WriteLine("Value is {0},result of operation is {1}", value, result);     
           }
       }    
   }

多播委托

    如果要調(diào)用多個方法,就需要多次顯示調(diào)用這個委托。但是,委托也可以包含多個方法。這種包含多個方法的委托就被稱為多播委托。如果調(diào)用多播委托,就可以按順序連續(xù)調(diào)用多個方法。為此,委托簽名就必須返回void。否則,就只能得到委托調(diào)用的最后一個方法結(jié)果。

    我們明顯可以利用的是返回類型為void的Action<double>委托:

    首先建立一MathOperations.cs類:

   

using System;    namespace Wrox.ProCSharp.Delegates   
    {    
      class MathOperations    
      {    
        public static void MultiplyByTwo(double value)    
        {    
          double result = value * 2;    
          Console.WriteLine("Multiplying by 2: {0} gives {1}", value, result);    
        }
        public static void Square(double value)   
        {    
          double result = value * value;    
          Console.WriteLine("Squaring: {0} gives {1}", value, result);    
        }    
      }    
    }

    然后建立調(diào)用當前方法的委托函數(shù):

   

using System;    namespace Wrox.ProCSharp.Delegates   
    {    
        class Program    
        {    
           static void Main()    
             {    
                Action<double> operations = MathOperations.MultiplyByTwo;    
                operations += MathOperations.Square;
                ProcessAndDispalyNumber(operations, 2.0);   
                ProcessAndDispalyNumber(operations, 7.94);    
                 ProcessAndDispalyNumber(operations, 1.414);    
                Console.WriteLine();    
                Console.ReadLine();    
            }    
          static void ProcessAndDispalyNumber(Action<double> action, double value)     
            {    
                Console.WriteLine();    
                Console.WriteLine("ProcessAndDispalyNumber called with value ={0}", value);    
                action(value);    
             }    
        }
    }

   注釋:多播委托可以識別運算符“+”和“+=”、“-”和“-=”。多播委托將兩個執(zhí)行方法通過“+”和“+=”連在一起(叫做擴展),還可以利用“-”和“-=”刪除方法調(diào)用。多播實際上是一個派生自System.MulticastDelegate的類,System.MuticastDelegate又派生自基類System.Delegate。System.MulticastDelegate的其他成員允許把多個方法調(diào)用鏈接一個列表。

  注意:如果使用多播委托,我們需要了解對同一個委托調(diào)用方法鏈的順序并未正式定義。因此應避免依賴于以特定順序調(diào)用方法代碼。還有個問題是多播委托包含一個逐個調(diào)用的委托集合。如果通過委托調(diào)用其中的一個方法拋出異常,則整個迭代都會停止。  
  下面將展示如果多播委托第一個拋出錯誤第二個方法無法正常執(zhí)行的案例:

 

using System;   
   using System.Collections.Generic;    
   using System.Linq;    
   using System.Text;  namespace Wrox.ProCSharp.Delegates   
   {    
    class Program    
    {    
      static void One()    
      {    
        Console.WriteLine("One");    
         throw new Exception("Error in one");    
      }
      static void Two()   
      {    
        Console.WriteLine("Two");    
      }    
      static void Main()    
      {    
          Action d1 = One;    
          d1 += Two;
          try   
          {    
              d1();    
          }    
           catch (Exception)    
          {    
              Console.WriteLine("Exception caught");    
          }    
       }    
     }    
    }

   注釋:one()方法我們故意加了個異常問題,那么執(zhí)行Main()的時候會自動的在One()方法執(zhí)行的時候進入catch

   我們?nèi)绻鉀Q上面的這種多播委托存在的問題的,我們可以使用Delegate定義GetInvocationList()方法,返回一個Delegate對象數(shù)組。然后循環(huán)迭代方法列表,捕獲異常進行下次迭代。下面代碼是重寫后的Main()函數(shù):

  

using System;   
   using System.Collections.Generic;    
   using System.Linq;    
   using System.Text;   namespace Wrox.ProCSharp.Delegates   
   {    
       class Program    
      {    
           static void One()    
           {    
               Console.WriteLine("One");    
                throw new Exception("Error in one");    
           }    
            static void Two()    
           {    
               Console.WriteLine("Two");    
           }    
           static void Main()    
           {         
               Action d1 = One;    
               d1 += Two;
               Delegate[] delegates = d1.GetInvocationList();   
               foreach (Action d in delegates)    
               {    
                   try    
                   {    
                       d.Invoke();    
                   }    
                   catch (Exception)    
                   {    
                        Console.WriteLine("Exception caught");    
                    }    
               }    
           }    
       }    
   }

匿名方法:

    這是C# 2.0之前的表示方式,在C#3.0里面可以使用lambda表達式代替匿名函數(shù)。我們熟悉的是要想使用委托工作,方法必須是已經(jīng)存在的。但是還有另外一種使用委托的方式:匿名方法

    實際上匿名方法與實例委托的區(qū)別只是在實例化委托的時候,那么我們首先先看下面的代碼是如何使用匿名方法的:

  

 using System;    namespace Wrox.ProCSharp.Delegates   
    {    
      class Program    
      {    
        static void Main()    
        {    
          string mid = ", middle part,";
          Func<string, string> anonDel = delegate(string param)   
           {    
            param += mid;    
            param += " and this was added to the string.";    
            return param;    
          };    
         Console.WriteLine(anonDel("Start of string"));
        }   
      }    
    }

     注釋:Func<string,string>委托接收一個字符串,返回一個字符串。anonDel是這種委托類型的變量。不是把方法名賦予給這個變量,而是使用一段簡單的代碼:它前面的關鍵字delegate,后面是一個字符串參數(shù)。該代碼塊使用方法級的字符串變量mid,該變量是匿名方法的外部定義的,并把它添加到要傳遞的參數(shù)中。接著代碼返回該字符串。在調(diào)用委托時,把一個字符串作為參數(shù)傳遞,將返回的字符串輸出輸出控制臺上。匿名方法的優(yōu)點是減少了要編寫的代碼。不必定義僅由委托使用的方法。在為事件定義委托時,這是非常明顯的。這有助于降低代碼的復雜性。尤其是定義了好幾個事件時,代碼會顯得比較簡單。代碼的執(zhí)行速度并沒有加快。

    在使用匿名方法的時候,必須遵循的原則有兩條:一是不能使用跳轉(zhuǎn)語句(break、go或continue)跳轉(zhuǎn)到該匿名方法的外部,反之亦然:匿名方法外部的跳轉(zhuǎn)語句不能跳轉(zhuǎn)到該匿名方法的內(nèi)部。二是在匿名方法的內(nèi)部不能訪問不安全的代碼,另外也不能訪問匿名方法外部使用的ref和out參數(shù)。但可以使用在匿名方法外部定義的其他變量。

    注意:如果需要用匿名方法多次編寫同一個功能,就不要使用匿名方法了。此時與復制代碼相比,編寫一個命名方法比較好,因為該方法只需要編寫一次,以后可以通過名稱引用它。

實戰(zhàn)的應用委托BubbleSorter

    為了說明委托的實際用途。我們編寫一個類BubbleSorter,它實現(xiàn)一個靜態(tài)方法Sort(),這個方法的第一參數(shù)是一個對象數(shù)組,把該數(shù)組按照升序重新排列。

    需求是本公司員工的工資需要按照需要的順序進行排序:

    首先定義一個BubbleSorter類:

  

 using System;   
    using System.Collections.Generic;    
    using System.Linq;    
    using System.Text;    namespace Wrox.ProCSharp.Delegates   
    {    
      class BubbleSorter    
      {    
        static public void Sort<T>(IList<T> sortArray, Func<T, T, bool> comparison)    
        {    
          bool swapped = true;    
         do    
         {    
           swapped = false;    
            for (int i = 0; i < sortArray.Count - 1; i++)    
            {    
              if (comparison(sortArray[i + 1], sortArray[i]))    
              {    
                T temp = sortArray[i];    
                 sortArray[i] = sortArray[i + 1];    
                sortArray[i + 1] = temp;    
                swapped = true;    
              }    
             }    
          } while (swapped);    
        }    
      }    
    }

    定義員工類,該員工類Employee.cs如下:

   

using System;   
    using System.Collections.Generic;    
    using System.Linq;    
    using System.Text;    namespace Wrox.ProCSharp.Delegates   
    {    
      class Employee    
      {    
        public Employee(string name, decimal salary)    
        {    
          this.Name = name;    
          this.Salary = salary;    
        }
        public string Name { get; private set; }   
        public decimal Salary { get; private set; }
        public override string ToString()   
        {    
          return string.Format("{0}, {1:C}", Name, Salary);    
        }
        public static bool CompareSalary(Employee e1, Employee e2)   
        {    
          return e1.Salary < e2.Salary;    
        }    
      }    
    }

    編寫客戶端代碼,完成我們需要排序的員工數(shù)據(jù):

   

using System;   
    using System.Collections.Generic;    
    using System.Linq;    
    using System.Text;    namespace Wrox.ProCSharp.Delegates   
    {    
      class Program    
      {    
        static void Main()    
        {    
          Employee[] employees =    
          {    
            new Employee("Bugs Bunny", 20000),    
            new Employee("Elmer Fudd", 10000),    
            new Employee("Daffy Duck", 25000),    
            new Employee("Wile Coyote", 1000000.38m),    
            new Employee("Foghorn Leghorn", 23000),    
            new Employee("RoadRunner", 50000)    
          };
          BubbleSorter.Sort(employees, Employee.CompareSalary);
          foreach (var employee in employees)   
          {    
            Console.WriteLine(employee);    
           }    
          Console.ReadLine();    
        }    
      }    
    }

    排序后我們員工的工資如下:

    Elmer Fudd, ¥10,000.00   
    Bugs Bunny, ¥20,000.00    
    Foghorn Leghorn, ¥23,000.00    
    Daffy Duck, ¥25,000.00    
    RoadRunner, ¥50,000.00    
    Wile Coyote, ¥1,000,000.38

   

說明:如果對您有所幫助請點贊,如果需要幫助請留言。


   

   


向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內(nèi)容。

AI