在前写过“浅谈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;
}
“`