第5章:事件与委托


本章目标

  1. 掌握委托的使用

  2. 掌握事件的使用

本章内容

什么是委托

委托是一种存储函数引用的类型,就像我们定义一个string str一样,这个str变量就是string类型。因为C#中没有函数类型,但是可以定义一个委托类型,把一个函数赋给这个委托,类似于C++中的函数指针 。

委托的定义与类的定义类似,先定义,再声明,再创建实例,再使用,定义时需要加上delegate关键字但是不需要函数体 。

与委托关联可以是任何类或者结构中的方法,可以是静态方法,只要是可以访问的方法都可以。创建一个委托类型使用关键字delegate(委托)

自定义委托

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CH05Demo
{
/// <summary>
/// 自定义委托
/// </summary>
/// <param name="s"></param>
public delegate void MyDelegate(string s);

internal class Program
{
static void Main(string[] args)
{
MyDelegate d1 = Test1;
MyDelegate d2 = Test2;
MyDelegate d3 = new MyDelegate(Test3);

d1("hello");
d2("hello");
d3("hello");

Console.ReadKey();
}

static void Test1(string s)
{
Console.WriteLine("调用了Test1()方法,参数:" + s);
}

static void Test2(string s)
{
Console.WriteLine("调用了Test2()方法,参数:" + s);
}

static void Test3(string s)
{
Console.WriteLine("调用了Test3()方法,参数:" + s);
}
}
}

Action委托

Action是系统内置的委托类型,这样就不需要通过delegate定义委托类型,可以直接使用Action作为类型 Action只能指向一个没有返回值的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CH05Demo
{

internal class Program
{
static void Main(string[] args)
{

//定义3个委托对象
Action action1 = Fun1;
Action<int> action2 = Fun2;
Action<int, string> action3 = Fun3;

//执行
action1();
action2(15);
action3(10, "hello");

Console.ReadKey();
}

static void Fun1()
{
Console.WriteLine("无参");
}
static void Fun2(int num)
{
Console.WriteLine($"num:{num}");
}
static void Fun3(int num, string str)
{
Console.WriteLine($"num:{num},str:{str}");
}
}
}

Func委托

Func是系统内置的委托类型,这样就不需要通过delegate定义委托类型,可以直接使用Func作为类型 Func只能指向有返回值的方法,最后一个泛型参数是返回值类型 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CH05Demo
{

internal class Program
{
static void Main(string[] args)
{

//定义3个委托对象
Func<int> action1 = Fun1;
Func<int,int> action2 = Fun2;
Func<int, string,int> action3 = Fun3;
Func<int, double, char, string> action4=Fun4;

//执行
action1();
action2(15);
action3(10, "hello");
action4(10, 3.14,'男');

Console.ReadKey();
}

static int Fun1()
{
Console.WriteLine("无参");
return 10;
}
static int Fun2(int num)
{
Console.WriteLine($"num:{num}");
return num*2;
}
static int Fun3(int num, string str)
{
Console.WriteLine($"num:{num},str:{str}");
return num * 3;
}
static string Fun4(int a, double b, char c)
{
Console.WriteLine($"a:{a},b:{b},c:{c}");

return "hello";
}
}
}

多播委托

多播委托是指一个委托指向多个方法,使用+=和-=去添加或移除委托,其实是调用了Delegate.Combine和Delegate.Remove方法,他会按照委托添加的顺序依次调用方法。对于有返回值的委托多播委托只能得到最后一个方法的返回结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CH05Demo
{
/// <summary>
/// 自定义委托
/// </summary>
/// <param name="s"></param>
public delegate void MyDelegate(string s);

internal class Program
{
static void Main(string[] args)
{
//定义委托对象
MyDelegate d1 = Test1;
d1 += Test2;
d1 += Test3;

d1("hello");

Console.ReadKey();
}

static void Test1(string s)
{
Console.WriteLine("调用了Test1()方法,参数:" + s);
}

static void Test2(string s)
{
Console.WriteLine("调用了Test2()方法,参数:" + s);
}

static void Test3(string s)
{
Console.WriteLine("调用了Test3()方法,参数:" + s);
}
}
}

使用匿名方法

匿名方法就是没有名称没有返回值的方法,任何使用委托变量的地方都可以使用匿名方法,但是因为没有名字所以不能自己调用自己而只能通过委托去调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CH05Demo
{
/// <summary>
/// 自定义委托
/// </summary>
/// <param name="s"></param>
public delegate void MyDelegate(string s);

internal class Program
{
static void Main(string[] args)
{
//使用匿名方法定义委托对象

Action de1 = delegate {
Console.WriteLine("de1");
};


Action<int> de2 = delegate (int num) {
Console.WriteLine("de2");
};

Action<int, int> de3 = delegate (int num1, int num2) {
Console.WriteLine("de3");
};

Func<int> de4 = delegate {
Console.WriteLine("de4");
return 11;
};


MyDelegate de5= delegate(string s) {
Console.WriteLine("de5");
};

//执行
de1();
de2(5);
de3(5,10);
de4();
de5("hello");

Console.ReadKey();
}
}
}

使用Lambda表达式

lambda表达式就是匿名方法的简写形式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CH05Demo
{
/// <summary>
/// 自定义委托
/// </summary>
/// <param name="s"></param>
public delegate void MyDelegate(string s);

internal class Program
{
static void Main(string[] args)
{
//使用Lambda表达式定义委托对象

Action de1 = ()=> Console.WriteLine("de1");

Action<int> de2 = (int num) => Console.WriteLine("de2");

Action<int, int> de3 = (int num1, int num2) => Console.WriteLine("de3");

Func<int> de4 = ()=> {
Console.WriteLine("de4");
return 11;
};


MyDelegate de5= delegate(string s) {
Console.WriteLine("de5");
};

//执行
de1();
de2(5);
de3(5,10);
de4();
de5("hello");

Console.ReadKey();
}
}
}

泛型委托

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CH05Demo
{
/// <summary>
/// 比较大小的委托
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="t1"></param>
/// <param name="t2"></param>
/// <returns></returns>
public delegate int DelCompare<T>(T t1, T t2);

class Program
{
static void Main(string[] args)
{

//定义委托对象
DelCompare<int> de = Compare1;

Console.WriteLine(de(35, 24));

Console.ReadKey();
}
public static int Compare1(int n1, int n2)
{
return n1 - n2;
}
}
}

利用委托作为参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CH05Demo
{
/// <summary>
/// 比较大小的委托
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="t1"></param>
/// <param name="t2"></param>
/// <returns></returns>
public delegate int DelCompare<T>(T t1, T t2);
class Program
{
static void Main(string[] args)
{
int[] nums = { 1, 2, 3, 4, 5 };
int max = GetMax<int>(nums, Compare1);
Console.WriteLine(max);

string[] names = { "abcdefg", "fdsfds", "fdsfdsfdsfdsfdsfdsfdsfsd", "sss" };
string max1 = GetMax<string>(names, (string s1, string s2) =>
{
return s1.Length - s2.Length;
});
Console.WriteLine(max1);
Console.ReadKey();
}

public static T GetMax<T>(T[] nums, DelCompare<T> del)
{
T max = nums[0];
for (int i = 0; i < nums.Length; i++)
{
//要传一个比较的方法
if (del(max, nums[i]) < 0)
{
max = nums[i];
}
}
return max;
}


public static int Compare1(int n1, int n2)
{
return n1 - n2;
}
}
}

事件

事件(Event) 基本上说是一个用户操作,如按键、点击、鼠标移动等等,或者是一些提示信息,如系统生成的通知。应用程序需要在事件发生时响应事件。例如,中断。

事件可以称为是一种特殊签名的委托 需要注意的是,委托可以在任何类中调用而事件只能在类的内部调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CH05Demo
{
class Program
{
static void Main(string[] args)
{
Student stu = new Student();

//注册事件:方式1
//stu.OnAgeCheck += new Student.AgeCheckHandler(()=> {
//Console.WriteLine("年龄不满足要求");
//});

//注册事件:方式2
//stu.OnAgeCheck += new Student.AgeCheckHandler(Age_Check); ;

//注册事件:方式3
stu.OnAgeCheck += Age_Check;

stu.Age = 130;
Console.WriteLine("年龄:" + stu.Age);


Console.ReadKey();
}

public static void Age_Check()
{
Console.WriteLine("年龄不满足要求");
}


}

class Student
{
//定义委托
public delegate void AgeCheckHandler();

//定义事件
public event AgeCheckHandler OnAgeCheck;

private int age;
public int Age
{
get
{
return this.age;
}
set
{
if (value > 0 && value < 100)
{
this.age = value;
}
else
{
if (this.OnAgeCheck != null)
{
//触发事件
this.OnAgeCheck();
}
}
}
}
}
}

本章总结

委托与事件的区别:

  • 委托是修饰的是一个类型,事件修饰的是一个对象
  • 委托可以作为方法参数传递,事件不能
  • 委托在赋值时无论类内还是类外都可以用=或+=赋值,事件在类内可以使用=,在类外只能通过+=赋值
  • 委托可以在类内和类外调用,事件只能在类内调用
  • 委托用于回调函数、方法引用参数的传递,事件用于观察者模式、消息订阅等

课后作业

1.自定义委托,包含两个string类型的参数, 返回类型为string,要求实现以下功能:
比较字符串的长短,返回较长的那个字符串.
(1)常规方法实现
(2)匿名方法实现
(3)lambda表达式实现

2.使用Func委托实现以下功能:
(1)无参,返回结果为学号(string类型,模板为”GCKJxxxx”)(lambda实现)
(2)包含参数:最小值,最大值,返回结果:指定范围的随机数(lambda实现)

3.自定义泛型委托,包含两个T类型的参数,返回类型为T,要求实现以下功能:
比较大小,
如果为数字,则直接比较,
如果为字符串,则比较其hashcode
如果为日期,则直接比较
lambda表达式实现

4.定义泛型委托实现排序,参数为T[],返回类型为void,要求实现以下功能(升序):
(1)对一组整数进行排序
(2)对一组字符串进行排序(比较hashcode)
(3)对一组学生对象进行排序(学生类包含:姓名、年龄、性别,根据学生的年龄排序)

5.定义泛型委托实现比较,参数1为T1,参数2为T2,返回类型为int;
定义泛型方法实现排序,参数1为T[] ,参数2为委托对象,返回类型为void,要求实现以下功能。(升序):
(1)对一组整数进行排序
(2)对一组字符串进行排序(比较hashcode)
(3)对一组学生对象进行排序(学生类包含:姓名、年龄、性别,根据学生的年龄排序)

6.在学生类中定义姓名赋值错误事件OnNameError,年龄赋值错误事件OnAgeError,性别赋值错误事件OnSexError.
要求在学生相关属性赋值错误时,触发对应事件。