C#委托的一次"甜蜜"接觸

来源:http://www.cnblogs.com/liunlls/archive/2016/12/17/delegate-use.html
-Advertisement-
Play Games

委托是個說爛了的話題,但是依舊有好多人不知道為什麼要在C 中使用委托,最近有朋友也問到我這個問題,所以舉例些場景,以供那些知道怎麼聲明委托、怎麼調用卻不知道為什麼要用的朋友一些參考,當然也是希望驗證下自己的理解是否正確。 如何聲明一個委托 委托使用關鍵字delegate,從外形上看和一個沒有方法體的 ...


委托是個說爛了的話題,但是依舊有好多人不知道為什麼要在C#中使用委托,最近有朋友也問到我這個問題,所以舉例些場景,以供那些知道怎麼聲明委托、怎麼調用卻不知道為什麼要用的朋友一些參考,當然也是希望驗證下自己的理解是否正確。

如何聲明一個委托

委托使用關鍵字delegate,從外形上看和一個沒有方法體的方法一樣,只不過是多了個關鍵字。

    public delegate void MyDelegateHandler();//無返回值,無參數
    public delegate int MyDelegateHandler1();//有返回值,無參數
    public delegate object MyDelegateHandler2(string name);//有返回值,有參數
    public delegate object MyDelegateHandler3(object first,ref int second,out float third, params object[] args);//有返回值,多個返回值

委托的聲明可以放在類的外面,也可以在類的內部

    public delegate object MyDelegateHandler2(string name);//有返回值,有參數
    static class Program
    {
        public delegate void MyDelegateHandler();//無返回值,無參數
    }

C#內置的委托類型

在.NET Framework中定義了大量的委托類型,像什麼WaitCallback、ParameterizedThreadStart、EventHandler、...,為了相容舊版本所以一直沒去掉,從.NET Framework 3.5後你可以使用Action、和Func(帶返回值)來更簡單的使用委托,他們都定義了大量的重載版本

委托的使用

下麵是流年寫的一個計算器類(比較簡陋),裡面有各種運算方法,每個方法只乾一件事,恩,滿足了單一原則,棒棒的!

    public class Calculator
    {
        public static int Add(int first, int second)
        {
            return first + second;
        }

        public static int Sub(int first, int second)
        {
            return first - second;
        }
    }

流年很簡單的就使用了這個類里的方法,哇,毫無壓力

 var result = Calculator.Add(1, 6);

現在問題來了“在做運算前,需要去驗證每個參數(假設這裡希望使用的參數都是正整數)”,╮(╯▽╰)╭每個方法都需要去加一段代碼

  public static int Add(int first, int second)
  {
      if (first <= 0 || second <= 0)
      {
          throw new ArgumentException("參數錯誤");
      }
      return first + second;
  }

一個方法一個方法的去改,很是麻煩,乾脆我把計算直接寫一個方法里,三下五除二,流年開始啪,啪,啪...

  public static int Calc(int first, int second, string operater)
  {
      if (first <= 0 || second <= 0)
      {
          throw new ArgumentException("參數錯誤");
      }
      int result = 0;
      if (operater.Equals("+"))
      {
          result = first + second;
      }
      if (operater.Equals("-"))
      {
          result = first - second;
      }
      return result;
  }

OK,搞定,很簡單嘛,但仔細一看,我靠Calc計算方法中幹了那麼多事情,又是加又是減的,如果計算器類還要添加對乘法的支持,還需要再修改這個方法,代碼耦合度太高了,這不就違背了對擴展開放,對修改關閉的原則了嘛。就在這時,天空烏雲密佈,一道閃電擊中了流年的腦袋...
首先我們來看,這段代碼的變化點是什麼?運算方法嘛
有什麼辦法可以隔離這種變化呢?委托嘛

 public static int Calc(int first, int second, Func<int, int, int> handler)
 {
     if (first <= 0 || second <= 0)
     {
         throw new ArgumentException("參數錯誤");
     }
     return handler.Invoke(first, second);//更簡單的寫法handler(first, second)
 }
  Func<int, int, int> calcHandler = new Func<int, int, int>(Calculator.Add);
  var result = Calculator.Calc(1, 6, calcHandler);

這樣不用去改變原來的方法,Add還是Add,減法運算還是減法運算,就算再添加乘/除演算法,直接添加加乘/除演算法相關的方法就行,也不用去改動原來的代碼了。而且演算法選擇邏輯也是交給的客戶端,而不是方法內部。將變化隔離了出去。

更簡單的使用委托

上面的委托實例聲明好麻煩,那我們再改進改進,可以將方法直接賦值給委托實例

  Func<int, int, int> calcHandler = Calculator.Add;
  var result = Calculator.Calc(1, 6, calcHandler);

再改進改進,直接將方法當做實參傳遞

 var result = Calculator.Calc(1, 6, Calculator.Add);

此刻,有沒有一種想把委托按在床上的衝動。不要著急,這還只是前戲...

Lambda表達式

在上面我們提到可以將一個方法直接賦值給實例,那麼是不是直接就可以將匿名方法直接賦值給委托實例呢?廢話不說,直接試試就知道了

 Func<int, int, int> calcHandler = delegate (int first, int second)
 {
     return first - second;
 };
 var result = Calculator.Calc(1, 6, calcHandler);

這就完了?當然沒有,讓我們繼續挑逗匿名方法

既然說,calcHandler 實例的引用是指向匿名方法的,那麼是不是可以直接將匿名方法直接滲入Calculator.Calc方法的參數中

 var result = Calculator.Calc(1, 6,delegate (int x, int y){return x - y;});

基於匿名函數,從Visual Studio 2010開始,微軟將匿名函數又升級成了Lambda表達式,代碼是越來越簡潔,連TMD方法都不用創建了,直接將演算法寫在調用上。

var result = Calculator.Calc(1, 6, (a, b) => a * b);

而且為了方便對集合類型的操作,微軟還封裝了大量的Linq擴展方法,這些都是基於委托實現的

 int[] numbers = { 11, 4, 3, 89, 5, 10 };
 //獲取集合中大於10的數字
 var query = numbers.Where(w => w > 10);

提供非同步調用

還是回到原來的代碼,Calculator類中委托調用的地方handler.Invoke(first, second),如果說委托實例對應的方法是個耗時的操作,我想我們誰也不想直接同步調用,讓程式傻傻的死在那裡,至少給用戶一些提示。為了處理這種問題,我們可以直接使用委托的非同步調用

    public class Calculator
    {
        public static int Add(int first, int second)
        {
            Console.WriteLine($"Add Thread Id {Thread.CurrentThread.ManagedThreadId}");
            Thread.Sleep(500);//模擬耗時的操作
            return first + second;
        }

        public static int Sub(int first, int second)
        {
            return first - second;
        }

        public static int Calc(int first, int second, Func<int, int, int> handler)
        {
            Console.WriteLine($"Calc Thread Id {Thread.CurrentThread.ManagedThreadId}");
            if (first <= 0 || second <= 0)
            {
                throw new ArgumentException("參數錯誤");
            }
            var ir = handler.BeginInvoke(first, second, null, null);
            Console.WriteLine("還在計算當中...");
            //等待計算結果
            return handler.EndInvoke(ir);
        }
    }


最後一行代碼handler.EndInvoke(ir),作用是等待非同步調用返回結果。他會一直阻塞線程直到非同步調用完成,然後返回計算的結果值。
委托非同步調用方法的返回值為一個IAsyncResult介面,我們可以通過該介面的實例屬性IsCompleted輪詢判斷非同步是否調用完成。
在非同步調用的方法參數中,有一個委托類型AsyncCallback,我們可以將一個函數傳給他,非同步方法執行完的時候會自動的去調用這個方法,這也就是所謂的回調函數。在回調函數中我們就可以幹些別的事情,比如定義個事件將結果傳遞出去

  //定義一個計算完成事件
  public static event Action<int> OnCalcCompelted;
   var ir = handler.BeginInvoke(first, second,
       (o) =>
       {
           var result = handler.EndInvoke(o);
           //通過事件通知註冊用戶計算已經完成,並將結果傳遞出去
           if (OnCalcCompelted!=null)
           {
               OnCalcCompelted(result);
           }
       }, 
       null);
   Console.WriteLine("還在計算當中...");

結語:
方法與委托就好比普通類與介面(抽象類)的關係。
編碼過程中委托並不一定是強制使用,他只不過是一種實現方式,在某些場景下比較合適,所以不要糾結於是要調用方法還是要通過委托調用,就像不懂設計模式也可以寫代碼完成功能,但是懂得這些套路之後你的代碼會更加有條理,更具有擴展性,當然逼格也越高。但是,我覺得不用模式套路的代碼逼格更高,誰都看不懂 O(∩_∩)O哈哈~

回到我們的Calculator類,如果需求是Calculator中只要實現加法運算,那TMD的誰還用委托,直接實現一個加法方法就行了,就這麼簡單。
涉及到委托的使用還不僅僅是這些,像什麼事件、表達式樹...每個都可以單獨作為主題來講,而且園子里也有很多講解的文章。流年水平有限,就簡單寫到這裡。



您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 方法不能跟變數一樣當參數傳遞,怎麼辦,C#定義了委托,就可以把方法當變數一樣傳遞了,為了簡單,匿名方法傳遞,省得再聲明方法了;再簡單,lambda表達式傳遞,比匿名方法更直觀。 public delegate int delegateArithmetic(int a, int b); //委托作為參 ...
  • 裝箱是將值類型轉換為 object 類型或由此值類型實現的任何介面類型的過程。 當 CLR 對值類型進行裝箱時,會將該值包裝到 System.Object 內部,再將後者存儲在托管堆上。 取消裝箱將從對象中提取值類型。 裝箱是隱式的;拆箱是顯式的。 裝箱和拆箱的概念是類型系統 C# 統一視圖的基礎, ...
  • 線程池的作用線程池,顧名思義,線程對象池。Task和TPL都有用到線程池,所以瞭解線程池的內幕有助於你寫出更好的程式。由於篇幅有限,在這裡我只講解以下核心概念: 線程池的大小 如何調用線程池添加任務 線程池如何執行任務 Threadpool也支持操控IOCP的線程,但在這裡我們不研究它,和task以 ...
  • 初學者,採用簡單三層架構,本來想用autofac依賴註入,只用sql server,沒打算遷移到別的資料庫,因此,簡單一點,就不用了。先做用戶管理模塊,登錄註冊等,客戶端cooke存儲,利用DESCryptoServiceProvider加密,源代碼下載地址:https://github.com/J ...
  • 最近悟出來一個道理,在這兒分享給大家:學歷代表你的過去,能力代表你的現在,學習代表你的將來。 十年河東十年河西,莫欺少年窮 學無止境,精益求精 C#洗牌演算法如下: 採用的是交換位置法,程式執行54次。效率還是頗高滴! @陳卧龍的博客 ...
  • 一、前言 在WPF編程中,有時候我們使用DataGrid會需要在一個DataColumn中既有TextBox,也要有ComboBox或者TextBlock等其他數據顯示樣式。 這個時候我們就需要DataGridTemplateColumn去自定義我們的Column樣式,通過數據類型去判斷該信息是以T ...
  • 在上一篇多線程(基礎篇2)中,我們主要講述了確定線程的狀態、線程優先順序、前臺線程和後臺線程以及向線程傳遞參數的知識,在這一篇中我們將講述如何使用C#的lock關鍵字鎖定線程、使用Monitor鎖定線程以及線程中的異常處理。 九、使用C#的lock關鍵字鎖定線程 1、使用Visual Studio 2 ...
  • 建立窗體的名稱修改為:Form_HoverTree文後附有源碼下載。主要代碼: 效果圖: 可以看出,這個窗體為自定義形狀的窗體,沒有標題欄。具體參考:http://hovertree.com/h/bjaf/52nadvt4.htm 源碼下載: http://hovertree.com/h/bjaf/ ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...