C#基礎---事件的使用
一:什麼是事件
事件是可以被控件識別的操作,如按下確定按鈕,選擇某個單選按鈕或者復選框。每一種控件有自己可以識別的事件,如窗體的加載、單擊、雙擊等事件,編輯框(文本框)的文本改變事件,等等。事件在桌面應用程序裡面無處可見,比如winform,WPF。。。,其次事件是基於委托而產生的。
二:事件的基本使用
1.事件的聲明: 其實和委托一樣只是多了一個Event而已。ShowMsg 就具備了ShowMsgHandler的功能。
Notes: 1. 委托可以依賴於一個類或者一個域名空間(C#基礎---委托的使用,裡面我有提到過), 而event必須依賴於一個類。否者無法聲明。
2. 委托可以用【=號】,而事件中只能用【+】或者【-】實現對方法的添加和刪除。當事件為空的時候調用【-】方法不會報錯。
public delegate void ShowMsgHandler(string str);
public event ShowMsgHandler ShowMsg;
2.事件基本使用: 其實基本用法和委托差不多。這裡有一點要說明,其實可以通過判斷是否為Null,來確定是否已經注冊了方法到事件或者委托。這個有時候勇於判斷事件是否該觸發。其次也發現事件第一次添加方法的時候是直接使用【+】號的,而不用像委托那樣第一次使用【=】號,後面的才使用【+】號。
復制代碼
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SpongeBobCoder.EventTest
{
public delegate void ShowMsgHandler(string str);
public class Program
{
public static event ShowMsgHandler ShowMsg;
public static void ShowName(string str)
{
Console.WriteLine("My Name is {0}", str);
}
public static void Main(string[] args)
{
Console.WriteLine(ShowMsg == null);
ShowMsg += ShowName;
ShowMsg("SopongeBob");
Console.WriteLine(ShowMsg == null);
Console.ReadKey();
}
}
}
復制代碼
3. 為何使用事件:
其實從上面來看事件和委托差不多。用法沒啥區別,但是為何還要使用委托呢? 在Codeplex看到的一篇文檔感覺挺不錯的。http://www.codeproject.com/Articles/7316/Events-and-Delegates ;小弟英語不好,英語好的就去看看原文,我的大概理解是: 好比一個App由一個項目組在開發,必定有一個項目經理,而這個項目經理的下面會有很多幫他做事的員工,其實經理就好比是一個委托,而每一位工作的員工就好比委托注冊的方法。項目開發完成了,拿給了用戶,用戶安裝好了。可是用戶發現軟件有缺陷,需要改進,而往往這個時候用戶是不會直接跟開發組的項目經理接觸的。往往會把意見給 維護部門,而由維護部門來告知相應開發組的項目經理。而通知這段過程就叫做事件。事件可以用來更好的封裝,和管理委托的。個人理解,這就是事件為何基於特定類的。不同類的 事件可以綁定同一個委托,從而注冊不同的方法。
3.1 先什麼一個Publisher類:類中有一個事件CalculatorEvent 和一個方法DoSomething,若事件被添加方法後,那麼將執行
復制代碼
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SpongeBobCoder.EventTest
{
public class Publisher
{
public event CalculatorHandler CalculatorEvent;
public void DoSomething(double num1,double num2)
{
if (CalculatorEvent != null)
{
CalculatorEvent(num1, num2);
}
}
}
}
復制代碼
3.2 然後看Program類,Main方法裡面聲明了兩個Publisher對象,分別是A,B。分別添加了AddNum和SumNum,運行結果是 3 和 -1。通過一個Publisher類可以對委托進行管理。
復制代碼
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SpongeBobCoder.EventTest
{
public delegate void CalculatorHandler(double num1,double num2);
public class Program
{
public static void AddNum(double num1, double num2)
{
Console.WriteLine("兩數之和為:{0}", num1 + num2);
}
public static void SubNum(double num1, double num2)
{
Console.WriteLine("兩數之差為:{0}", num1-num2);
}
public static void Main(string[] args)
{
Publisher pubA = new Publisher();
Publisher pubB = new Publisher();
pubA.CalculatorEvent += AddNum;
pubB.CalculatorEvent += SubNum;
pubA.DoSomething(1, 2);
pubB.DoSomething(1, 2);
Console.ReadKey();
}
}
}
復制代碼
三:事件的使用
1.異常處理: 事件可以注冊多個方法,可是要是其中有一個方法拋出異常了怎麼辦,一旦拋出異常了。那麼當前執行程序被中斷,那麼後面注冊的方法就沒法執行了。問題來了,那些方法可以解決這個問題呢? 方法一,保證所注冊的方法不會出現異常,這一塊我們是無法預知。 方法二。將注冊的方法的異常都吃掉,其中微軟提供了兩個方法GetInvocationList和DynamicInvoke方法:
復制代碼
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SpongeBobCoder.EventTest
{
public class Publisher
{
public event CalculatorHandler CalculatorEvent;
public void DoSomething(double num1, double num2)
{
if (CalculatorEvent != null)
{
Delegate[] delArray = CalculatorEvent.GetInvocationList(); //獲取到所有的委托方法.
foreach (Delegate del in delArray)
{
try
{
object obj = del.DynamicInvoke(num1, num2); //obj是獲取每個方法的返回值,如果聲明的是無返回值的委托,那麼obj==null
Console.WriteLine(obj == null);
}
catch (Exception e) // 把異常吃掉
{
Console.WriteLine(e.InnerException.Message);
}
}
}
}
}
}
復制代碼
2.異步調用:對於前面的注冊的時間,都是順序執行,那如何實現異步執行呢,各個注冊之間不相互干擾,其實微軟提供了一個BeginInvoke方法可以解決這個問題.
Public IAsyncResult BeginInvoke (
InvokeArgs invokeArgs, // 這一部分對於的是委托
AsyncCallback callback,// 回調方法,注冊方法執行完後,將會執行回調方法
Object userState // 傳遞的參數
)
上面理解起來可能有點困難下面來看看代碼吧:
Program類: AddNum 方法有5秒的延時。SubNum沒有添加延時,注冊順序是 AddNum,SubNum
復制代碼
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
namespace SpongeBobCoder.EventTest
{
public delegate double CalculatorHandler(double num1, double num2);
public class Program
{
public static double AddNum(double num1, double num2)
{
Thread.Sleep(TimeSpan.FromSeconds(5));
Console.WriteLine("兩數之和為:{0}", num1 + num2);
return num1 + num2;
}
public static double SubNum(double num1, double num2)
{
Console.WriteLine("兩數之差為:{0}", num1 - num2);
return num1 - num2;
}
public static void Main(string[] args)
{
Publisher pubA = new Publisher();
pubA.CalculatorEvent += AddNum;
pubA.CalculatorEvent += SubNum;
pubA.DoSomething(1, 5);
Console.ReadKey();
}
}
}
復制代碼
publisher類: 注意看紅色方法,前面兩個參數是與委托對應的。後面的MyCallBack是回調的方法,
復制代碼
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Remoting.Messaging;
using System.Text;
using System.Threading.Tasks;
namespace SpongeBobCoder.EventTest
{
public class Publisher
{
public event CalculatorHandler CalculatorEvent;
public void DoSomething(double num1, double num2)
{
if (CalculatorEvent != null)
{
Delegate[] delArray = CalculatorEvent.GetInvocationList(); //獲取到所有的委托方法.
foreach (Delegate del in delArray)
{
try
{
CalculatorHandler handler = del as CalculatorHandler;
IAsyncResult myResult = handler.BeginInvoke(num1, num2, MyCallback, "方法執行完畢,回調成功" + handler.Method.Name);
// Console.WriteLine("SpongeBob"); 這塊代碼不注釋的話是先執行這段代碼的輸出,然後才會輸出其他的。這一塊我不知道為什麼
}
catch (Exception e) // 把異常吃掉
{
Console.WriteLine(e.InnerException.Message);
}
}
}
}
public void MyCallback(IAsyncResult asyncResult)
{
AsyncResult result = (AsyncResult)asyncResult;
CalculatorHandler handler = (CalculatorHandler)result.AsyncDelegate;
Console.WriteLine(asyncResult.AsyncState);
Console.WriteLine("獲取到執行結果為:{0} \n", handler.EndInvoke(asyncResult));
}
}
}
復制代碼
運行結果: 其實先執行的是 SubNum,已經達到了異步的效果,其中通過EndInvoke也在回調函數中獲取到了委托的返回值。
codezip:http://files.cnblogs.com/FourLeafCloverZc/CSharp.zip
總結: 以前最開始的對事件理解不清楚,記得當時在winform的時候跨線程獲取數據就要用Invoke來獲取,不然老是提示線程不安全。本次博客中我發現了兩個問題需要個各位博友幫忙解答
1. 在異步調用的代碼中有一段代碼被我注解了。其實我發現了一個問題, 如果 Console.WriteLine("SpongeBob"); 不注解,運行的情況是,先輸出兩行(“SpongeBob”) 然後再輸出 上圖的運行結果。不清楚是什麼原因,求大神指點。
2. 對於下面代碼如果調用的傳入的參數是1,0 ; 是不會報錯的。運行結果是兩數相除無窮大。應該會報除數不能為0的異常呀。
public static void DivNum(double num1, double num2)
{
Console.WriteLine("兩數相除為:{0}", num1 / num2);
}