1. 程式人生 > >通過一個WPF例項進一步理解委託和事件

通過一個WPF例項進一步理解委託和事件

在前寫過“淺談C#中的委託”和“淺談C#中的事件”兩篇部落格,內容有些抽象,似乎難以說明委託和事件的關係。

今天通過一個小程式來進一步說明二者的使用及聯絡。

首先新建一個WPF應用程式,取名TestDelegateAndEvent。
在.xmal中加入四個按鈕,並新增Window_Loaded事件。
這裡寫圖片描述
程式碼如下:

<Window x:Class="TestDelegateAndEvent.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" Loaded="Window_Loaded">
<Grid> <Button Content="執行委託" Height="39" HorizontalAlignment="Left" Margin="128,53,0,0" Name="button1" VerticalAlignment="Top" Width="254" Click="button1_Click" /> <Button Content="干擾委託" Height
="39" HorizontalAlignment="Left" Margin="128,118,0,0" Name="button2" VerticalAlignment="Top" Width="254" Click="button2_Click" />
<Button Content="執行事件" Height="39" HorizontalAlignment="Left" Margin="128,181,0,0" Name="button3" VerticalAlignment="Top" Width="254" /> <Button Content
="干擾事件" Height="39" HorizontalAlignment="Left" Margin="128,241,0,0" Name="button4" VerticalAlignment="Top" Width="254" />
</Grid> </Window>

接下來是編寫.cs程式碼:
定義了一個委託,並對委託進行了掛載函式

public class Test
{
    public delegate void MyDelegate(); 
    //建立一個委託例項 
    public MyDelegate myDel; 

}

Test test = new Test();
public MainWindow() 
{
     InitializeComponent(); 
} 
//方法A 
public void Fun_A() 
{ 
    MessageBox.Show("A 方法觸發了"); 
} 
//方法B 
public void Fun_B() 
{ 
    MessageBox.Show("B 方法觸發了"); 
} 
//方法C 
public void Fun_C() 
{ 
    MessageBox.Show("C 方法觸發了"); 
} 

private void Window_Loaded(object sender, RoutedEventArgs e) 
{ 
    //註冊委託(掛載方法) 
    test.myDel += Fun_A; 
    test.myDel += Fun_B; 

} 
private void button1_Click(object sender, RoutedEventArgs e)
 { 
     test.myDel(); 
 } 
 private void button2_Click(object sender, RoutedEventArgs e)
 { 
      test.myDel = null; 
      test.myDel += Fun_C; 
 } 

執行以上程式碼,點選執行委託,彈出了兩個messagebox。點選干擾委託,然後再點選執行委託,就彈出了一個messagebox,即只有”c方法被觸發了”。我們可以看到,一切都是由myDel = null引起的。

委託本質就是一個類, 它包含一對有用的欄位,第一個欄位是存放該物件的引用,第二個欄位存放一個方法的指標,所以我們可以把委託理解成一個指向函式的指標,當一個方法作為引數 賦值給一個委託的時候,該委託就指向了該方法的首地址,即方法名,所以當我們給委託註冊A,B兩個方法,該委託就同時指向了A,B兩個方法的首地址,但這 是又突然給委託賦值了,且賦值了一個null物件,注意這裡用的是賦值符號[=],這就是說讓該委託清除原有的指標指向,此時指向一個null,之後又給 委託註冊了C方法,所以此時委託即指向null,又指向了C方法的首地址,這就是為什麼執行時只會看到C方法被觸發的原因了!

那就是說現在的委託變得不安全了,哪天一個專案中給委託註冊了很多方法,但突然被幹擾了下,前面的註冊都失效了,那我們前面做的工作不是白做了,那有沒有辦法可以防止這種干擾呢??答案是當然有,相信聰明的你也應該猜到了,這時就是事件該上場的時候了。

我們對程式碼進行重寫,加入了事件。
在Window_Loaded函式中加入程式碼:
test.EventMyDel += Fun_A;
test.EventMyDel += Fun_B;
當我們在Button4_Click中加入程式碼test.EventMyDel = null時,編譯器會報錯:
錯誤 CS0070: 事件“TestDelegateAndEvent.MainWindow.Test.EventMyDel”只能出現在 += 或 -= 的左邊(從型別“TestDelegateAndEvent.MainWindow.Test”中使用時除外)。
這說明了在事件中允許使用 = 運算子進行訂閱。

public class Test
{
    public delegate void MyDelegate(); 
    //建立一個委託例項 
    public MyDelegate myDel; 
    //宣告一個事件
    public event MyDelegate EventMyDel;
    //事件觸發機制(必須和事件在同一個類中) 外界無法直接用EventMyDel()來觸發事件
    public void DoEventDel()
    {
        EventMyDel()
    }
}

Test test = new Test();
public MainWindow() 
{
     InitializeComponent(); 
} 
//方法A 
public void Fun_A() 
{ 
    MessageBox.Show("A 方法觸發了"); 
} 
//方法B 
public void Fun_B() 
{ 
    MessageBox.Show("B 方法觸發了"); 
} 
//方法C 
public void Fun_C() 
{ 
    MessageBox.Show("C 方法觸發了"); 
} 

private void Window_Loaded(object sender, RoutedEventArgs e) 
{ 
    //註冊委託(掛載方法) 
    test.myDel += Fun_A; 
    test.myDel += Fun_B; 
    test.EventMyDel += Fun_A;
    test.EventMyDel += Fun_B;

} 
private void button1_Click(object sender, RoutedEventArgs e)
 { 
     test.myDel(); 
 } 
 private void button2_Click(object sender, RoutedEventArgs e)
 { 
      test.myDel = null; 
      test.myDel += Fun_C; 
 } 
 private void button3_Click(object sender, RoutedEventArgs e) 
 { 
     test.DoEventMyDel(); 
 } 
 private void button4_Click(object sender, RoutedEventArgs e) 
 { 
     test.EventMyDel += null; 
     test.EventMyDel += Fun_C; 
}

通過上述程式碼可以看出,委託可以使用“=”,而事件不可以使用“=”。即事件是對委託的一種封裝,也就像是屬性與欄位的關係。

此時需要特別注意一下上述的錯誤,如果不寫Test類,即不進行封裝。那麼如果在與事件相關程式碼位於同一個類中的程式碼註冊事件時,使用“=”是可以的,下面的程式碼就不會報錯!!!
即:


    public delegate void MyDelegate(); 
    //建立一個委託例項 
    public MyDelegate myDel; 
    //宣告一個事件
    public event MyDelegate EventMyDel;
    //事件觸發機制(必須和事件在同一個類中) 外界無法直接用EventMyDel()來觸發事件
    public void DoEventDel()
    {
        EventMyDel()
    }
public MainWindow() 
{
     InitializeComponent(); 
} 
//方法A 
public void Fun_A() 
{ 
    MessageBox.Show("A 方法觸發了"); 
} 
//方法B 
public void Fun_B() 
{ 
    MessageBox.Show("B 方法觸發了"); 
} 
//方法C 
public void Fun_C() 
{ 
    MessageBox.Show("C 方法觸發了"); 
} 

private void Window_Loaded(object sender, RoutedEventArgs e) 
{ 
    //註冊委託(掛載方法) 
    myDel += Fun_A; 
    myDel += Fun_B; 
    //註冊事件
    EventMyDel += Fun_A;
    EventMyDel += Fun_B;

} 
private void button1_Click(object sender, RoutedEventArgs e)
 { 
     myDel(); 
 } 
 private void button2_Click(object sender, RoutedEventArgs e)
 { 
      myDel = null; 
      myDel += Fun_C; 
 } 
 private void button3_Click(object sender, RoutedEventArgs e) 
 { 
     DoEventMyDel(); 
 } 
 private void button4_Click(object sender, RoutedEventArgs e) 
 { 
     EventMyDel = null;//**不會報錯** 
     EventMyDel += Fun_C; 
}

“`