C#中的委托和事件
一:什么是委托
——委托是一种存储函数引用的类型,就像我们定义一个string str一样,这个str变量就是string类型。因为C#中没有函数类型,但是可以定义一个委托类型,把一个函数赋给这个委托,类似于C++中的函数指针
——委托的定义与类的定义类似,先定义,再声明,再创建实例,再使用,定义时需要加上delegate关键字但是不需要函数体
using System;
class MainClass
{
//定义
delegate void Fun1Del();
//声明
Fun1Del del1;
static void Main()
{
del1 = Fun1();
del1();
}
static void Fun1() { }
}
——当定义一个委托时,CLR会自动生成一个类,继承自System.MulticastDelegate,包含了构造函数、BeginInvoke、EndInvoke、Invoke,所以委托本质是一个Class。事件也类似,但是事件只对外部提供了一个add和一个remove接口,所以不能在外部调用方法
例如上面的委托例子,de1=Fun1()就相当于调用了构造函数,通过构造函数将Fun1方法的内存地址存起来。调用del1()时相当于调用了Invoke方法去调用当前存储的内存地址的方法
二:自定义委托
using System;
class MainClass
{
delegate void Fun1Del();
delegate void Fun2Del(int num);
delegate int Fun3Del(int num);
static void Main()
{
Fun1Del del1 = Fun1;
Fun2Del del2 = Fun2;
Fun3Del del3 = Fun3;
Fun1();
Fun2(11);
int num = Fun3(11);
}
static void Fun1() { }
static void Fun2(int num) { }
static int Fun3(int num) { return num; }
}
三:Action委托
Action是系统内置的委托类型,这样就不需要通过delegate定义委托类型,可以直接使用Action作为类型
Action只能指向一个没有返回值的方法
using System;
class MainClass
{
static void Main()
{
Action action1 = Fun1;
Action<int> action2 = Fun2;
Action<int, string> action3 = Fun3;
action1();
action2(11);
action3(11, "hi");
}
static void Fun1() { }
static void Fun2(int num) { }
static void Fun3(int num, string str) { }
}
四:Func委托
Func是系统内置的委托类型,这样就不需要通过delegate定义委托类型,可以直接使用Func作为类型
Func只能指向有返回值的方法,最后一个泛型参数是返回值类型
using System;
class MainClass
{
static void Main()
{
Func<int> action1 = Fun1;
Func<int, int> action2 = Fun2;
Func<int, string, int> action3 = Fun3;
action1();
action2(11);
action3(11, "hi");
}
static int Fun1() { return 11; }
static int Fun2(int num) { return 11; }
static int Fun3(int num, string str) { return 11; }
}
五:多播委托
多播委托是指一个委托指向多个方法,使用+=和-=去添加或移除委托,其实是调用了Delegate.Combine和Delegate.Remove方法,他会按照委托添加的顺序依次调用方法。对于有返回值的委托多播委托只能得到最后一个方法的返回结果
using System;
class MainClass
{
static void Main()
{
Action action1 = Fun1;
action1 += Fun2;
action1();
Func<int> action2 = Fun3;
action2 += Fun4;
Console.WriteLine(action2());
}
public static void Fun1() { Console.WriteLine("fun1"); }
public static void Fun2() { Console.WriteLine("fun2"); }
public static int Fun3() { Console.WriteLine("fun3"); return 11; }
public static int Fun4() { Console.WriteLine("fun4"); return 111; }
}
使用GetInvocationList方法可以获取到当前委托中所有方法的委托
六:匿名方法
匿名方法就是没有名称没有返回值的方法,任何使用委托变量的地方都可以使用匿名方法,但是因为没有名字所以不能自己调用自己而只能通过委托去调用
using System;
class MainClass
{
static void Main()
{
Action action1 = delegate { };
Action<int> action2 = delegate (int num) { };
Action<int, int> action3 = delegate (int num1, int num2) { };
Func<int> action4 = delegate { return 11; };
}
}
七:Lambda表达式
lambda表达式就是匿名方法的简写形式
using System;
class MainClass
{
static void Main()
{
Action action1 = () => { };
Action<int> action2 = (num) => { };
Action<int, int> action3 = (num1, num2) => { };
Func<int> action4 = () => { return 11; };
}
}
八:事件
事件可以称为是一种特殊签名的委托
需要注意的是,委托可以在任何类中调用而事件只能在类的内部调用
using System;
using System.Linq;
class MainClass
{
static void Main()
{
Test test = new Test();
test.action1();//可以调用
test.action2();//报错,不能在类的外部调用
}
}
public class Test
{
public Action action1;
public event Action action2;
}
九:烧水案例
例如一个烧水壶,当水温超过95度时警报器会报警告诉你水的温度,显示器会显示当前的水温
using System;
class MainClass
{
static void Main()
{
Kettle kettle = new Kettle();
kettle.BoilWater();
}
}
public class Kettle
{
private int temperature = 96;
public void BoilWater()
{
if (temperature > 95)
{
Alert(temperature);
Show(temperature);
}
}
private void Alert(int param)
{
Console.WriteLine("警告!!" + param);
}
private void Show(int param)
{
Console.WriteLine("当前温度:" + param);
}
}
上面的例子虽然能完成我们之前描述的工作,但是却不够完美,首先烧水壶由三部分组成:烧水器、警报器、显示器,它们来自于不同厂商并进行了组装,那么,应该是烧水器仅仅负责烧水,它不能发出警报也不能显示水温,在水烧开时由警报器发出警报,由显示器显示水温,下面使用观察者模式对上面的列子进行优化
using System;
class MainClass
{
static void Main()
{
Kettle kettle = new Kettle();
Alarm alarm = new Alarm();
Display display = new Display();
kettle.BoilEvent += alarm.Alert;
kettle.BoilEvent += display.Show;
kettle.BoilWater();
}
}
public class Kettle
{
private int temperature = 96;
public delegate void BoilHandler(int param);
public event BoilHandler BoilEvent;
public void BoilWater()
{
if (temperature > 95)
{
BoilEvent(temperature);
}
}
}
public class Alarm
{
public void Alert(int param)
{
Console.WriteLine("警告!!" + param);
}
}
public class Display
{
public void Show(int param)
{
Console.WriteLine("当前温度:" + param);
}
}
假如现在不只是想要获得烧水壶当前的温度,还想知道生厂商,那么就需要改动烧水壶整个类,使用.Net规范的写法如下
using System;
class MainClass
{
static void Main()
{
Kettle kettle = new Kettle();
Alarm alarm = new Alarm();
Display display = new Display();
kettle.BoilEvent += alarm.Alert;
kettle.BoilEvent += display.Show;
kettle.BoilWater();
}
}
public class Kettle
{
public class KettleEventArgs : EventArgs
{
public readonly int temperature;
public KettleEventArgs(int temperature)
{
this.temperature = temperature;
}
}
int temperature = 96;
public string producer = "boshi";
public delegate void BoiledEventHandler(object sender, KettleEventArgs e);
public event BoiledEventHandler BoilEvent;
public void BoilWater()
{
if (temperature > 95)
{
KettleEventArgs e = new KettleEventArgs(temperature);
BoilEvent(this, e);
}
}
}
public class Alarm
{
public void Alert(object sender, Kettle.KettleEventArgs e)
{
Console.WriteLine("警告!!" + e.temperature);
}
}
public class Display
{
public void Show(object sender, Kettle.KettleEventArgs e)
{
Console.WriteLine("当前温度:" + e.temperature);
}
}
十:委托和事件的区别
——委托是修饰的是一个类型,事件修饰的是一个对象
——委托可以作为方法参数传递,事件不能
——委托在赋值时无论类内还是类外都可以用=或+=赋值,事件在类内可以使用=,在类外只能通过+=赋值
——委托可以在类内和类外调用,事件只能在类内调用
——委托用于回调函数、方法引用参数的传递,事件用于观察者模式、消息订阅等