通過一個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;
}
“`