c#中的數據類型簡介(委托)

来源:http://www.cnblogs.com/qqxz/archive/2016/03/16/5274804.html
-Advertisement-
Play Games

委托是一種類型,它封裝了一類方法,這些方法具有相同的方法簽名(signature)。定義聽起來有點拗口,首先可以確定委托是一種數據類型,那麼什麼是方法簽名,其實就是指方法的輸入參數列表和輸出參數類型。來看下麵的例子,類deleMthod定義了3個方法,add、minus和multi,他們都具有相同的


c#中的數據類型簡介(委托)

什麼是委托?

       委托是一種類型,它封裝了一類方法,這些方法具有相同的方法簽名(signature)。定義聽起來有點拗口,首先可以確定委托是一種數據類型,那麼什麼是方法簽名,其實就是指方法的輸入參數列表和輸出參數類型。來看下麵的例子,類deleMthod定義了3個方法,add、minus和multi,他們都具有相同的輸入參數列表(int x,int y)和輸出參數類型int,那麼我們就說這三個方法具有相同的方法簽名。開發者可以抽象地用 int 某名稱(int x,int y) 的一種類型對方法進行封裝,在c#中這種抽象的數據類型叫委托,針對上述的幾個方法我們可以定義委托 : public delegate int Handler(int x ,int y),public 是一個訪問修飾符,delegate關鍵字表示這是一個委托,int Hander(int x,int y)表示這個委托的名稱。

  class deleMethod
    {
        public int add(int x, int y)
        {
            return x + y;
        }
        public int minus(int x, int y)
        {
            return x - y;
        }
        public int multi(int x, int y)
        {
            return x * y;
        }
    }

怎麼使用委托

      使用委托大體有四個步驟:

  • 定義一個委托,上節已經提及。
  • 定義委托方法,上節deleMethod類中add、minus、multi都是委托方法,定義的目的就是為了使用它,講專業點就是為了方法的調用
  • 委托變數及賦值,和類型一樣,在使用前需要對變數賦值。
  • 委托變數的使用。

      怎樣定義委托變數,還是接著上面的例子。我們已經定義了一個委托類型 public delegate int Handler(int x,int y),和c#語法規範一樣定義一個變數並賦值語法是:“類型名  變數名 = new 類型名(方法);”,如上例

“Handler deleCall = new Handler(方法名);“,在.net2.0後對於委托的實例化可以簡化為” Handler deleCall = 方法名;“。

委托變數的使用,也就是對委托方法的調用。其語法是”int result1 = deleCall(10,20);“或者使用委托調用方法 Invoke,“int result2 = deleCall.Invoke(10,20);”。

具體如何使用可以看看下麵的示例:

    class Program
    {
        public delegate int Handler(int x, int y);                 //---定義委托的類型,可以將委托看成一種特殊的數據類型

        static void Main(string[] args)
        {                                                   
            deleMethod dm = new deleMethod();                      //---實例化包含委托方法的類型
            Handler  deleCall = new Handler(dm.add);               //---定義委托變數delCall,並出示化賦值
            int result1 = deleCall(10, 20);                        //---實例方法的調用invoke
            Console.WriteLine("the add resutl is:{0}", result1);
            deleCall = dm.minus;
            int result2 = deleCall.Invoke(12, 6);
            Console.WriteLine("the minus result is:{0}", result2);
            Console.ReadLine();
        }
    }

如上例所示,定義一個簡單的加、減功能如此的複雜,攪來攪去讓人頭,真是無語,難怪很多朋友談委托色變暈。在實際使用的過程中,c#還是有很多方式幫我們簡化代碼。

簡化委托

預定義的泛型委托

      c#系統最常見的預定義的委托類型有三種,Func<>委托、Action<>委托、Predicate<>委托,Func<>委托是一個有返回值的委托,輸入參數可以多達16個;而Action<>委托是一個沒有返回值的委托,它的輸入參數也可以多達16個;而Predicate<>是一個具有bool返回類型的委托,它只運行一個輸入參數。對於有上例的委托類型,我們可以使用預定義的委托類型Fun<int,int,int>來代替,省去我們自己定義一個什麼鬼東西 public delegate int Handler(int x,int y)類型,其代碼其實可以簡化為如下例所示:

namespace DelegateDemo1
{
    class Program
    {
        static void Main(string[] args)
        {
            deleMethod dm = new deleMethod();
            Func<int, int, int> fun = dm.add;                   //---使用預定義的委托類型Func<>
            int result4 = fun(8, 10);
            Func<int, int, int> fun1 = dm.minus;
            int result5 = fun1(12, 8);
            Console.WriteLine("預定義的委托輸出{0},{1}", result4, result5);
            Console.ReadLine();
        }
    }

    class deleMethod
    {
        public int add(int x, int y)
        {
            return x + y;
        }
        public int minus(int x, int y)
        {
            return x - y;
        }
        public int multi(int x, int y)
        {
            return x * y;
        }
    }
}

我把委托的方法定義和委托的調用放在一起看,是不是比原先自己定義的一個委托類型簡單方便一些?但是這樣使用委托還是不怎麼清爽,估計在實際應用中很少人會怎麼寫代碼,太不方便了。

匿名委托

      當委托實例的調用和委托方法的定義分開處理,碼農們在讀程式代碼的時候需要來回的去找委托方法去匹配委托變數,看看參數列表和返回值是否正確,這樣的程式代碼的可讀性很差。其實c#還是有方法讓我們簡化代碼:那就是匿名委托,將方法體直接在委托的實例化時給出,之所以叫匿名委托就是再定義委托的時候省略掉委托的名稱,它的定義語法是delegate(參數1,參數2) 後面直接就給出方法體,用大括弧將方法體括起。剛看起來比較怪異,接觸多了也就習慣了,莫有辦法只能去適應c#的語法規範。話說多了是水,還不如看代碼來得直接。

namespace DelegateDemo1
{
    class Program
    {
        static void Main(string[] args)
        {
            Func<int, int, int> fun = delegate(int x, int y) { return x + y; };
            Func<int, int, int> fun1 = delegate(int x, int y) { return x - y; };
            int result4 = fun(8, 10);
            int result5 = fun1(12, 8);
            Console.WriteLine("預定義的委托輸出{0},{1}", result4, result5);
            Console.ReadLine();
        }
    }
}

看看是不是在原來的基礎上大幅度減少了代碼量,腫麽辦,是否代碼量已經減少到極致了?

lambda表達式

      其實對於委托的定義還可以進一步簡化,那就是使用lambda表達式,lambda表達式的定義是(參數列表)=>{方法體},=>讀作goes to。lambda表達式對參數列表和方法表達式的精簡達到極致,對於上面的例子,用λ表達式可以省略掉匿名委托的關鍵字和參數類型,系統可以進行類型推斷,不影響運行,其簡化的代碼如下。對.net 1.0,2.0最傳統的委托定義和使用,是一個巨大的簡化,它剔除了所有多餘的語句達到極致。

namespace DelegateDemo1
{
    class Program
    {
        static void Main(string[] args)
        {
            Func<int, int, int> fun = (x, y) => x + y;
            Func<int, int, int> fun1 = (x, y) => x - y; 
            int result4 = fun(8, 10);
            int result5 = fun1(12, 8);
            Console.WriteLine("預定義的委托輸出{0},{1}", result4, result5);
            Console.ReadLine();
        }
    }
}

 委托鏈

      前面講過,委托在本質上仍然是一個類,我們用delegate關鍵字聲明的所有委托都繼承自System.MulticastDelegate。後者又是繼承自System.Delegate類,System.Delegate類則繼承自System.Object。一個委托可以綁定若幹相同簽名的方法形成一個委托鏈,委托鏈是一個委托實例的集合,它允許我們調用這個集合中的委托實例所代表的所有方法(對於有返回值的方法,委托鏈的返回值為鏈表中最後一個方法的返回值),在下麵的例子我們定義的委托方法都沒有返回值。我們可以用 GetInvocationList()方法獲取委托鏈。

 class Program
    {
        static void Main(string[] args)
        {
            //Action 表示沒有返回值的一類方法
            Action<int, int> actionA = (x, y) =>
            {
                Console.WriteLine("x是{0},y是{1},他們的平方和是{2}", x, y, x * x + y * y);
            };
            actionA += (x, y) =>
            {
                Console.WriteLine("x是{0},y是{1},他們的平方差是{2}", x, y, x * x - y * y);
            };
            actionA(10, 5);

              foreach (var item in actionA.GetInvocationList())
              Console.WriteLine(item.Method);

            Console.ReadLine();
        }
    }

 什麼是事件

      經常看到一種定義是:事件是一種特殊的委托,是對委托的封裝。其實這種定義是很不嚴謹的。委托是一種數據類型,但是事件只是委托的實例,不能算是一種數據類型,所以說事件是一種特殊的委托是不准確的。如果這樣定義:事件是一種特殊的委托實例,是對委托的封裝。那麼在C#中事件是如何定義並被使用的呢?其實事件從定義到使用要經過四個階段。

  • 定義事件依賴的委托,並定義事件以及引發事件的方法,該步驟可以定義在事件發佈器類中
  • 定義事件所依賴的事件方法,該步驟可以定義在事件訂閱者類中
  • 如果有事件參數,該步驟可以定義在事件參數類中
  • 註冊事件並引發事件,該步驟一般寫在主程式中

      一般來講在事件定義過程中,各個方法的命名沒有統一的標準,但我們可以參考微軟在c#中的命名規範進行命名

建議事件委托命名為:事件+EventHandler。如果有額外的參數傳遞需定義自己的參數類型,參數類型的命名規範 :事件+EventHandler。比如,可以定義一個事件委托 public  delegate void calcEventHandler(object sender,calcEventArgs e); 。

定義一個事件變數如:public event calcEventHandler calc;

定義一個引發事件的方法如:public void onCalc(object sender,calcEventArgs e){}

建議方法名為 on+事件,就如同我們在開發web程式時,綁定的事件名有onClick,onLoad等一樣。

參考看下麵的例子,瞭解定義一個事件的一般過程,如果不需要傳遞事件的參數可以省去事件參數類的定義,使用系統預定義的EventArgs就可以了。

    /// <summary>
    /// 主程式類
    /// </summary>
    class Program
    {
        static void Main(string[] args)
        {
            eventPublish myEvent = new eventPublish();
            eventSubscription myMethod = new eventSubscription();
            //綁定方法 add 和 subtract,又稱為對事件方法的註冊 -=稱為對事件方法的註銷
            myEvent.calc += myMethod.add;
            myEvent.calc += myMethod.substract;
            while (true)
            {
                try
                {
                    Console.WriteLine("請輸入第一個整數數");
                    int numA = Convert.ToInt16(Console.ReadLine());
                    Console.WriteLine("請輸入第二個整數");
                    int numB = Convert.ToInt16(Console.ReadLine());
                    calcEventArgs e = new calcEventArgs(numA, numB);
                    //在本例不需要sender參數,隨意傳遞了字元串"sender"
                    myEvent.onCalc("sender", e);    
                }
                catch (Exception ex)
                {
                    Console.WriteLine("出現錯誤," + ex.Message);
                }
            }
        }
    }
    /// <summary>
    /// 定義一個事件發佈器類
    /// </summary>
    class eventPublish
    {
        //定義一個委托類型,委托名一般是 事件變數名+EventHandler
        public delegate void calcEventHander(object sender,calcEventArgs e);
        //定義一個事件變數,其變數名為calc
        public event calcEventHander calc;
        //封裝事件,對外定義了引發事件的方法,定義的引發方法名一般是 on+事件變數名
        public void onCalc(object sender, calcEventArgs e)
        {
            if (calc != null)
                calc(sender,e);
        }
    }
    /// <summary>
    /// 定義一個事件訂閱者類(事件方法類)
    /// </summary>
    class eventSubscription
    {
        public void add(object sender, calcEventArgs e)
        {
           Console.WriteLine("兩數相加等於{0}",   e.X + e.Y );
        }
        public void substract(object sender, calcEventArgs e)
        {
            Console.WriteLine("兩數相減等於{0}", e.X - e.Y);
        }
    }
    /// <summary>
    /// 定義一個事件參數類
    /// </summary>
    class calcEventArgs : EventArgs
    {
        private int _x;
        private int _y;
        public calcEventArgs(int x, int y)
        {
            this._x = x;
            this._y = y;
        }

        public int X
        {
            get { return _x; }
        }
        public int Y
        {
            get { return _y; }
        }
    }

我們將事件是對委托的封裝,如果用ILDAS反編譯可執行文件查看中間代碼就可以非常明瞭的看出事件運行機制

如上圖所示,事件calc,經.net運行時編譯為中間語言後產生了一個private 的calc屬性,同時也自動生成了add_calc和remove_calc方法,用於註冊和註銷訂閱者方法。因為事件是被封裝的,儘管calc屬性是private,但在所以在發佈器類內部可以用 calc(sender,e)這樣的方法直接調用;但在主程式中如果這樣使用就會出錯,只能通過onCalc方法進行間接調用。

後記

  本篇文章,一開始提出了什麼是委托的疑問,通過引入幾個方法來講述委托是什麼以加強對委托概念的理解。第二部分講述了使用委托的四個步驟,並通過示例闡述了這幾個步驟。第三部分講述了委托使用的簡化問題,通過使用泛型委托簡化自定義委托,通過使用匿名委托可以簡化定義委托方法。匿名委托是在定義委托的時候直接給出方法體,通過使用lambda表達式的類型推斷可進一步簡化委托的使用。第四部分講述了委托鏈,通過綁定方法初始化委托,並通過+=綁定更多的委托方法。第五部分講述了事件的定義和使用的四個步驟。當然委托的使用場景還有很多,比如通過BeginInvoke和EndInvoke進行非同步調用,因不是本篇文章的重點,所以文章中沒有提及。

 


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

-Advertisement-
Play Games
更多相關文章
  • 本文闡述C#中相等性比較,其中主要集中在下麵兩個方面 ==和!=運算符,什麼時候它們可以用於相等性比較,什麼時候它們不適用,如果不使用,那麼它們的替代方式是什麼? 什麼時候,需要自定一個類型的相等性比較邏輯 在闡述相等性比較,以及如何自定義相等性比較邏輯之前,我們首先瞭解一下值類型比較和引用類型比較
  • CheckBox控制項,由於它的值是選擇與非選擇。因此在提交數據時,想讓用戶必須選擇CheckBox,普通情況之下,不好做驗證。 但我們可以使用asp:CustomValidator來驗證,不過還得寫自定義驗證Javascript代碼,可參考如下:
  • Mysql操作 調用
  • 項目中用到了EF Code First和遷移,但發現有些方面似懂非懂。比如:如何在遷移文件中控制遷移過程?如果在遷移文件中執行SQL語句?如何使用Update-Database的其它參數?資料庫在生產環境的時候如何遷移?於是就有了下麵的這些體驗:enable-migration第一次生成資料庫時使用
  • 摘要: 介紹緩存的基本概念和常用的緩存技術,給出了各種技術的實現機制的簡單介紹和適用範圍說明,以及設計緩存方案應該考慮的問題(共17頁) · 性能——將相應數據存儲起來以避免數據的重覆創建、處理和傳輸,可有效提高性能。比如將不改變的數據緩存起來,例如國家列表等,這樣能明顯提高web程式的反應速度;
  • 效果: 編寫節目類 屬性:播出時間、時段、名稱、視頻路徑 編寫頻道基類 屬性:頻道名稱、頻道節目單位置、節目列表 抽象方法:Fetch() 編寫頻道子類 繼承“頻道基類”,實現Fetch()【只寫方法聲明】 編寫頻道工廠類 方法:實現創建頻道子類 第一步.創建幾個類: 01:創建一個電視類(TvPr
  • 在進行 .NET Web MVC 框架開發的網站程式的時候,我們都會遇到最關鍵的問題,數據傳輸。 .NET MVC 4中的ControllerBase類建議我們用ViewBag動態數據字典形式(type:dynamic,JavaScript就是動態語言,只在運行時候才進行類型判斷,而不是在編譯),或
  • 這學期學了Web技術這門課,但對這門課是做什麼的、有什麼用處並不瞭解,教材是Asp.net實用網站開發,對我這樣的初學者大概是深了一點,所以決定對Web技術的背景知識做下整理。 1.Web工作原理 Web全稱為World Wide Web(WWW)。簡單地說,Web是網際網路提供的一種服務,通過它可以
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...