[ASP.NET MVC 大牛之路]02 - C#高級知識點概要(1) - 委托和事件

来源:http://www.cnblogs.com/xiaoxiaojia/archive/2016/04/28/5441751.html
-Advertisement-
Play Games

在ASP.NET MVC 小牛之路系列中,前面用了一篇文章提了一下C#的一些知識點。照此,ASP.NET MVC 大牛之路系列也先給大家普及一下C#.NET中的高級知識點。每個知識點不太會過於詳細,但足矣。要深入研究還需要去查看更多的專業資料。 要成為大牛,必然要有扎實的基本功,不然時間再長項目再多 ...


在ASP.NET MVC 小牛之路系列中,前面用了一篇文章提了一下C#的一些知識點。照此,ASP.NET MVC 大牛之路系列也先給大家普及一下C#.NET中的高級知識點。每個知識點不太會過於詳細,但足矣。要深入研究還需要去查看更多的專業資料。

要成為大牛,必然要有扎實的基本功,不然時間再長項目再多也很難有大的提升。本系列講的C# 高級知識點,是非常值得去撐握的,不僅可以讓你寫代碼時游刃有餘,而且去研究和學習一些開源項目時,也不會顯得那麼吃力了。

希望大家記住,這裡講的所有的知識點,不僅僅是瞭解了就可以了,還要會靈活用,一定要多思考,撐握其中的編程思想。

本文講的是委托和事件,這兩個詞可能你早就耳熟能詳,但你是否真正撐握了呢?

本系列講的C#高級知識點都是要求開發時能達到可以徒手寫出來的水平(不依賴搜索引擎、找筆記等)。建議開發時儘量自己寫(時間允許的話),如果覺得自己寫的不好,再Google。寫多了就自然會靈活運用。

本文目錄:http://www.jinhusns.com/Products/Download/?type=xcj

委托

    委托的簡單使用

    用委托實現插件式編程

    多播委托

    靜態方法和實例方法對於委托的區別

    泛型委托

    Func 和 Action 委托

    委托的相容

事件

    事件的基本使用

    事件的標準模式

結尾

 

委托

委托太常見了,能靈活運用可以使你在編程中游刃有餘。

簡單說它就是一個能把方法當參數傳遞的對象,而且還知道怎麼調用這個方法。

委托的簡單使用

一個委托類型定義了該類型的實例能調用的一類方法,這些方法含有同樣的返回類型和同樣參數(類型和個數相同)。委托和介面一樣,可以定義在類的外部。如下定義了一個委托類型 - Calculator:

delegate int Calculator (int x);

此委托適用於任何有著int返回類型和一個int類型參數的方法,如:

static int Double (int x) { return x * 2; }

創建一個委托實例,將該此方法賦值給該委托實例:

Calculator c = new Calculator(Double);

也可以簡寫成:

Calculator c = Double;

這個方法可以通過委托調用:

int result = c(2);

下麵是完整代碼:

delegate int Calculator(int x);class Program {    static int Double(int x) { return x * 2; }    static void Main(string[] args) {
        Calculator c = Double;        int result = c(2);

        Console.Write(result);
        Console.ReadKey();
    }
}

用委托實現插件式編程

我們可以利用“委托是一個能把方法作為參數傳遞的對象”這一特點,來實現一種插件式編程。

例如,我們有一個Utility類,這個類實現一個通用方法(Calculate),用來執行任何有一個整型參數和整型返回值的方法。這樣說有點抽象,下麵來看一個例子:

delegate int Calculator(int x);class Program {    static int Double(int x) { return x * 2; }    static void Main(string[] args) {        int[] values = { 1,2,3,4};
        Utility.Calculate(values, Double);        foreach (int i in values)
            Console.Write(i + " "); // 2 4 6 8
        Console.ReadKey();
    }
}class Utility {    public static void Calculate(int[] values, Calculator c) {        for (int i = 0; i < values.Length; i++)
            values[i] = c(values[i]);
    }
}

這個例子中的Utility是固定不變的,程式實現了整數的Double功能。我們可以把這個Double方法看作是一個插件,如果將來還要實現諸如求平方、求立方的計算,我們只需向程式中不斷添加插件就可以了。

如果Double方法是臨時的,只調用一次,若在整個程式中不會有第二次調用,那麼我們可以在Main方法中更簡潔更靈活的使用這種插件式編程,無需先定義方法,使用λ表達式即可,如:

...
Utility.Calculate(values, (x) => { return x * 2; });
...

以後我們會經常寫這樣的代碼。

多播委托

所 有的委托實例都有多播的功能。所謂多播,就像一群程式員在瞬聘網填好了求職意向後,某天有個公司發佈了一個和這些程式員求職意向剛好相匹配的工作,然後這 些求職者都被通知了 - “有一份好工作招人啦,你們可以直接申請去上班了!”。PS:為了公司,我也算滿拼的。懇請大家允許我在博文中不忘提到瞬聘網。在後續博文中會有不少案例 確實是我在瞬聘網系統開發時使用過的。:)

也就是說,一個委托實例不僅可以指向一個方法,還可以指向多個方法。例如:

MyDelegate d = MyMethod1;// “+=” 用來添加,同理“-=”用來移除。d += MyMethod2;// d -= MyMethod2

調用時,按照方法被添加的順序依次執行。註意,對於委托,+= 和 -= 對null是不會報錯的,如:

MyDelegate d;
d += MyMethod1;// 相當於MyDelegate d = MyMethod1;

為 了更好的理解多播在實際開發中的應用,我用模擬瞬聘網的職位匹配小工具來做示例。在職位匹配過程中會有一段處理時間,所以在執行匹配的時候要能看到執行的 進度,而且還要把執行的進度和執行情況寫到日誌文件中。在處理完一個步驟時,將分別執行兩個方法來顯示和記錄執行進度。

我們先定義一個委托(ProgressReporter),然後定義一個匹配方法(Match)來執行該委托中的所有方法。如下:

public delegate void ProgressReporter(int percentComplete);public class Utility {    public static void Match(ProgressReporter p) {        if (p != null) {            for (int i = 0; i <= 10; i++) {
                p(i * 10);
                System.Threading.Thread.Sleep(100);
            }
        }
    }
}

然後我們需要兩個監視進度的方法,一個把進度寫到Console,另一個把進度寫到文件。如下:

class Program {    static void Main(string[] args) {
        ProgressReporter p = WriteProgressToConsole;
        p += WriteProgressToFile;
        Utility.Match(p);
        Console.WriteLine("Done.");
        Console.ReadKey();
    }    static void WriteProgressToConsole(int percentComplete) {
        Console.WriteLine(percentComplete+"%");
    }    static void WriteProgressToFile(int percentComplete) {
        System.IO.File.AppendAllText("progress.txt", percentComplete + "%");
    }
}

運行結果:

看到這裡,是不是發現你已然更加愛上C#了。

靜態方法和實例方法對於委托的區別

當一個類的實例的方法被賦給一個委托對象時,在上下文中不僅要維護這個方法,還要維護這個方法所在的實例。System.Delegate 類的Target屬性指向的就是這個實例。舉個例子:

class Program {    static void Main(string[] args) {
        X x = new X();
        ProgressReporter p = x.InstanceProgress;
        p(1);
        Console.WriteLine(p.Target == x); // True
        Console.WriteLine(p.Method); // Void InstanceProgress(Int32)    }    static void WriteProgressToConsole(int percentComplete) {
        Console.WriteLine(percentComplete+"%");
    }    static void WriteProgressToFile(int percentComplete) {
        System.IO.File.AppendAllText("progress.txt", percentComplete + "%");
    }
}class X {    public void InstanceProgress(int percentComplete) {        // do something    }
}

但對於靜態方法,System.Delegate 類的Target屬性是Null,所以將靜態方法賦值給委托時性能更優。

泛型委托

如果你知道泛型,那麼就很容易理解泛型委托,說白了就是含有泛型參數的委托,例如:

public delegate T Calculator<T> (T arg);

我們可以把前面的例子改成泛型的例子,如下:

public delegate T Calculator<T>(T arg);class Program {    static int Double(int x) { return x * 2; }    static void Main(string[] args) {        int[] values = { 1, 2, 3, 4 };
        Utility.Calculate(values, Double);        foreach (int i in values)
            Console.Write(i + " "); // 2 4 6 8
        Console.ReadKey();
    }
}class Utility {    public static void Calculate<T>(T[] values, Calculator<T> c) {        for (int i = 0; i < values.Length; i++)
            values[i] = c(values[i]);
    }
}

Func 和 Action 委托

有了泛型委托,就有了一能適用於任何返回類型和任意參數(類型和合理的個數)的通用委托,Func 和 Action。如下所示(下麵的in表示參數,out表示返回結果):

delegate TResult Func <out TResult> ();delegate TResult Func <in T, out TResult> (T arg);delegate TResult Func <in T1, in T2, out TResult> (T1 arg1, T2 arg2);
... 一直到 T16delegate void Action ();delegate void Action <in T> (T arg);delegate void Action <in T1, in T2> (T1 arg1, T2 arg2);
... 一直到 T16

有了這樣的通用委托,我們上面的Calculator泛型委托就可以刪掉了,示例就可以更簡潔了:

public static void Calculate<T>(T[] values, Func<T,T> c) {    for (int i = 0; i < values.Length; i++)
        values[i] = c(values[i]);
}

Func 和 Action 委托,除了ref參數和out參數,基本上能適用於任何泛型委托的場景,非常好用。

委托的相容

1. 委托的類型相容

delegate void D1();delegate void D2();
...
D1 d1 = Method1;
D2 d2 = d1;

下麵是被允許的:

D2 d2 = newD2 (d1);

對於具體相同的目標方法的委托是被視為相等的:

delegate void D();
...
D d1 = Method1;
D d2 = Method1;
Console.WriteLine (d1 == d2); // True

同理,對於多播委托,如果含有相同的方法和相同的順序,也被視為相等。

2. 參數類型相容

在OOP中,任何使用父類的地方均可以用子類代替,這個OOP思想對委托的參數同樣有效。如:

delegate void StringAction(string s);class Program {    static void Main() {
        StringAction sa = new StringAction(ActOnObject);
        sa("hello");
    }    static void ActOnObject(object o) {
        Console.WriteLine(o); // hello
    }
}

3. 返回值類型相容

道理和參數類型相容一樣:

delegate object ObjectRetriever();class Program {    static void Main() {
        ObjectRetriever o = new ObjectRetriever(RetriveString);        object result = o();
        Console.WriteLine(result); // hello    }    static string RetriveString() { return "hello"; }
}

事件

當我們使用委托場景時,我們很希望有這樣兩個角色出現:廣播者和訂閱者。我們需要這兩個角色來實現訂閱和廣播這種很常見的場景。

廣播者這個角色應該有這樣的功能:包括一個委托欄位,通過調用委托來發出廣播。而訂閱者應該有這樣的功能:可以通過調用 += 和 -= 來決定何時開始或停止訂閱。

事件就是描述這種場景模式的一個詞。事件是委托的一個子集,為了滿足“廣播/訂閱”模式的需求而生。

事件的基本使用

聲明一個事件很簡單,只需在聲明一個委托對象時加上event關鍵字就行。如下:

public delegate void PriceChangedHandler (decimal oldPrice, decimal newPrice);public class IPhone6
{    public event PriceChangedHandler PriceChanged;
}

事件的使用和委托完全一樣,只是多了些約束。下麵是一個簡單的事件使用例子:

public delegate void PriceChangedHandler(decimal oldPrice, decimal newPrice);public class IPhone6 {    decimal price;    public event PriceChangedHandler PriceChanged;    public decimal Price {        get { return price; }        set {            if (price == value) return;            decimal oldPrice = price;
            price = value;            // 如果調用列表不為空,則觸發。
            if (PriceChanged != null)
                PriceChanged(oldPrice, price);
        }
    }
}class Program {    static void Main() {
        IPhone6 iphone6 = new IPhone6() { Price = 5288 };        // 訂閱事件
        iphone6.PriceChanged += iphone6_PriceChanged;        // 調整價格(事件發生)
        iphone6.Price = 3999;

        Console.ReadKey();
    }    static void iphone6_PriceChanged(decimal oldPrice, decimal price) {
        Console.WriteLine("年終大促銷,iPhone 6 只賣 " + price + " 元, 原價 " + oldPrice + " 元,快來搶!");
    }
}

運行結果:

有人可能會問,如果把上面的event關鍵字拿掉,結果不是一樣的嗎,到底有何不同?

沒錯可以用事件的地方就一定可以用委托代替。

但 事件有一系列規則和約束用以保證程式的安全可控,事件只有 += 和 -= 操作,這樣訂閱者只能有訂閱或取消訂閱操作,沒有許可權執行其它操作。如果是委托,那麼訂閱者就可以使用 = 來對委托對象重新賦值(其它訂閱者全部被取消訂閱),甚至將其設置為null,甚至訂閱者還可以直接調用委托,這些都是很危險的操作,廣播者就失去了獨享 控制權。

事件保證了程式的安全性和健壯性。

事件的標準模式

.NET 框架為事件編程定義了一個標準模式。設定這個標準是為了讓.NET框架和用戶代碼保持一致。System.EventArgs是標準模式的核心,它是一個沒有任何成員,用於傳遞事件參數的基類。
按照標準模式,我們對於上面的iPhone6示例進行重寫。首先定義EventArgs:

public class PriceChangedEventArgs : EventArgs {    public readonly decimal OldPrice;    public readonly decimal NewPrice;    public PriceChangedEventArgs(decimal oldPrice, decimal newPrice) {
        OldPrice = oldPrice;
        NewPrice = newPrice;
    }
}

然後為事件定義委托,必須滿足以下條件:

  • 必須是 void 返回類型;

  • 必須有兩個參數,且第一個是object類型,第二個是EventArgs類型(的子類);

  • 它的名稱必須以EventHandler結尾。

由於考慮到每個事件都要定義自己的委托很麻煩,.NET 框架為我們預定義好一個通用委托System.EventHandler<TEventArgs>:

public delegate void EventHandler<TEventArgs> (object source, TEventArgs e) where TEventArgs : EventArgs;

如果不使用框架的EventHandler<TEventArgs>,我們需要自己定義一個:

public delegate void PriceChangedEventHandler (object sender, PriceChangedEventArgs e);

如果不需要參數,可以直接使用EventHandler(不需要<TEventArgs>)。有了EventHandler<TEventArgs>,我們就可以這樣定義示例中的事件:

public class IPhone6 {
    ...    public event EventHandler<PriceChangedEventArgs> PriceChanged;
    ...
}

最後,事件標準模式還需要寫一個受保護的虛方法來觸發事件,這個方法必須以On為首碼,加上事件名(PriceChanged),還要接受一個EventArgs參數,如下:

public class IPhone6 {
    ...    public event EventHandler<PriceChangedEventArgs> PriceChanged;    protected virtual void OnPriceChanged(PriceChangedEventArgs e) {        if (PriceChanged != null) PriceChanged(this, e);
    }
    ...
}

下麵給出完整示例:

public class PriceChangedEventArgs : System.EventArgs {    public readonly decimal OldPrice;    public readonly decimal NewPrice;    public PriceChangedEventArgs(decimal oldPrice, decimal newPrice) {
        OldPrice = oldPrice;
        NewPrice = newPrice;
    }
}public class IPhone6 {    decimal price;    public event EventHandler<PriceChangedEventArgs> PriceChanged;    protected virtual void OnPriceChanged(PriceChangedEventArgs e) {        if (PriceChanged != null) PriceChanged(this, e);
    }    public decimal Price {        get { return price; }        set {            if (price == value) return;            decimal oldPrice = price;
            price = value;            // 如果調用列表不為空,則觸發。
            if (PriceChanged != null)
                OnPriceChanged(new PriceChangedEventArgs(oldPrice, price));
        }
    }
}class Program {    static void Main() {
        IPhone6 iphone6 = new IPhone6() { Price = 5288M };        // 訂閱事件
        iphone6.PriceChanged +=iphone6_PriceChanged;        // 調整價格(事件發生)
        iphone6.Price = 3999;
        Console.ReadKey();
    }    static void iphone6_PriceChanged(object sender, PriceChangedEventArgs e) {
        Console.WriteLine("年終大促銷,iPhone 6 只賣 " + e.NewPrice + " 元, 原價 " + e.OldPrice + " 元,快來搶!");
    }
}

運行結果:

 

結尾

委托和事件的知識比較多,所以我單獨寫成一篇。由於委托和事件將來會經常用到(尤其是委托),所以建議大家一定要撐握,用到的時候能夠自己寫得出來。

有些人可能會比較急,希望我直接寫ASP.NET MVC的示例。如果是這樣,你永遠成為不了大牛,將來開發的時候你只會Google找答案找例子。

既然我開始寫大牛系列,那麼我就有責任指引那些認真閱讀和理解並加以實踐的人走向大牛之路。你不認真沒能成為大牛是你自己的錯,你認真了直到本系列文章結束也沒能成為大牛那就是我的錯。


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

-Advertisement-
Play Games
更多相關文章
  • 任務描述:通過科大訊飛語音合成組件線上完成文本轉語音的合成,然後再轉換為電話系統IVR要求的音頻格式: wave mu-law 16位 8kHZ 64kbps。 完成步驟: 首先,我們要先通過科大訊飛語音合成組件實現文本合成,由於科大訊飛提供的介面都是C語言的,如果用C#調用需要做二次封裝,為了快速 ...
  • .Net 動態代理,AOP 直接上代碼了。 DEMO: 也可以到我的Github上,直接獲取完整項目 https://github.com/jinshuai/DynamicProxy.NET ...
  • 官方開髮指導https://autopoco.codeplex.com/documentation 初步使用: SimpleUser是自己要批量創建的類 1)創建管理工廠 IGenerationSessionFactory factory = AutoPocoContainer.Configure( ...
  • 是一般處理程式, 是asp.net web 組件的一種,ashx是其擴展名。 實現IHttpHandler介面,接收並處理http請求。這個介面有一個IsReusable成員,一個待實現的方法ProcessRequest(HttpContextctx) 。程式在processRequest方法中處理 ...
  • 1.【開源】C#跨平臺物聯網通訊框架ServerSuperIO(SSIO) 2.應用SuperIO(SIO)和開源跨平臺物聯網框架ServerSuperIO(SSIO)構建系統的整體方案 3.C#工業物聯網和集成系統解決方案的技術路線(數據源、數據採集、數據上傳與接收、ActiveMQ、Mongod ...
  • [源碼下載] 背水一戰 Windows 10 (10) - 資源: StaticResource, ThemeResource 作者:webabcd介紹背水一戰 Windows 10 之 資源 StaticResource ThemeResource 示例1、演示“StaticResource”相關 ...
  • 最近遇到了一個非常邪門的故障,重新安裝了Windows2008伺服器後 Mp4無法正常播放; 整個互聯網中關於設置MP4的方法基本都是教你如何在IIS中設置MIME 本文討論的不是如何教你設置MIME,如果當你配置完MIME後仍然無法播放MP4,那麼您就要檢查一下是否安裝了《安全狗》軟體。 當前時間 ...
  • Linq的delegate表達式,Insus.NET覺得它封裝得好,讓開發時簡化了很多代碼,而且容易閱讀與檢索。 比如,我們需要計算優惠給客戶金額,打85%折,可以這樣寫: using System; using System.Collections.Generic; using System.Li ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...