C# 委托 Delegate
Delegate 类表示委托,委托是一种数据结构,它引用静态方法或引用类实例及该类的实例方法。
委托的声明、实例化和调用
声明
以下实例声明为Del的委托,该委托采用字符串作为参数,并返回void的方法:
public delegate void Del(string message);
实例化
Delegate的实例化和Class的实例化基本类似。
以下实例是,创建一个Del委托的实例。因为该委托声明是:采用字符串作为参数,并返回void的方法,所以实例化该委托时需要传递一个符号条件的方法作为参数:
Del handler = new Del(DelegateMethod);
上面代码可以简写为以下代码:
Del handler = DelegateMethod;//简写,只写方法就可以,不用使用new关键字
为委托创建一个DelegateMethod 方法(该方法采用字符串作为参数,并返回void):
void DelegateMethod(string message)
{
System.Console.WriteLine(message);
}
调用
调用方式:
Del handler = new Del(DelegateMethod);
handler.Invoke("Hello World");
简写方式(就像将DelegateMethod方法赋值给一个变量,然后通过传递参数的方式执行这个变量):
Del handler = DelegateMethod;
handler("Hello World");
泛型委托--Func Action
因为委托实际上就是一个方法的类型,所以 .NET Framework根据泛型内置了Action与Func两种形式的委托。
Action
Action是无返回值的泛型委托。
- Action 表示无参,无返回值的委托。如下:
public delegate void Action();
- Action<int,string> 表示有传入参数int,string无返回值的委托
- Action<int,string,bool> 表示有传入参数int,string,bool无返回值的委托
- Action<int,int,int,int> 表示有传入4个int型参数,无返回值的委托
- ·······
Action至少0个参数,至多16个参数,无返回值。
Func
Func是有返回值的泛型委托。
- Func
表示无参,返回值为int的委托
public delegate TResult Func<out TResult>();
- Func<object,string,int> 表示传入参数为object, string 返回值为int的委托
- Func<object,string,int> 表示传入参数为object, string 返回值为int的委托
- Func<T1,T2,,T3,int> 表示传入参数为T1,T2,,T3(泛型)返回值为int的委托
- ·······
Func至少0个参数,至多16个参数,根据返回值泛型返回。必须有返回值,不可void
委托的意义
解耦
例如我们有这样一个需求,有一组学生数据,需要根据条件查询出相应的学生列表,在不使用委托的时候可能我们会写出以下的代码:
学生类:
public class Student
{
public string Name { get; set; }
public int Age { get; set; }
public string Email { get; set; }
public Student(string name, int age, string email)
{
this.Name = name;
this.Age = age;
this.Email = email;
}
public Student() { }
public void Study()
{
Console.WriteLine("我正在学习……");
}
}
实现代码:
List<Student> students = new List<Student>();
students.Add(new Student("Oliver", 18, "123@qq.com"));
students.Add(new Student("小白", 19, "123@sina.com"));
students.Add(new Student("老K", 28, "123@163.com"));
students.Add(new Student("土豆", 16, "232@sina.com"));
students.Add(new Student("番茄", 33, "fanqie@qq.com"));
students.Add(new Student("小红", 22, "24424@163.com"));
students.Add(new Student("黎明", 18, "2232@126.com"));
students.Add(new Student("黄昏", 21, "huanghun@qq.com"));
{
//找出20岁以上的学生
List<Student> newList1 = new List<Student>();
foreach (var item in students)
{
if (item.Age > 20)
{
newList1.Add(item);
}
}
Console.WriteLine("查询到条数:" + newList1.Count);
//找出邮箱长度大于10位的学生
List<Student> newList2 = new List<Student>();
foreach (var item in students)
{
if (item.Email.Length > 10)
{
newList2.Add(item);
}
}
Console.WriteLine("查询到条数:" + newList2.Count);
//找出年龄在20岁以上并且邮箱长度大于10位的学生
List<Student> newList3 = new List<Student>();
foreach (var item in students)
{
if (item.Age > 20 && item.Email.Length > 10)
{
newList3.Add(item);
}
}
Console.WriteLine("查询到条数:" + newList3.Count);
}
经过我们的分析,发现下面的代码在上面重复了3次,都是循环然后进行逻辑判断。
foreach (var item in students)
{
if (....)
{
newList.Add(item);
}
}
然后我们通过使用委托,将逻辑封装到一个方法中,然后将方法传递进去,这样我们就减少耦合:
//定义一个条件委托
delegate bool WhereDelegate(Student s);
//判断年龄是否大于20
bool GetAgeThan(Student s)
{
return s.Age > 20;
}
//判断邮箱长度是否大于10
bool GetEmailLen(Student s)
{
return s.Email.Length > 10;
}
//判断是否年龄在20岁以上并且邮箱长度大于10位
bool GetAgeThanAndEmailLen(Student s)
{
return s.Age > 20 && s.Email.Length > 10;
}
/// <summary>
/// 根据逻辑条件筛选数据
/// </summary>
/// <param name="list">列表</param>
/// <param name="where">逻辑条件</param>
/// <returns>筛选结果</returns>
List<Student> StudentWhere(List<Student> list, WhereDelegate where)
{
List<Student> newList = new List<Student>();
foreach (var item in list)
{
if (where(item))
{
newList.Add(item);
}
}
return newList;
}
调用:
//找出20岁以上的学生
List<Student> newList1 = StudentWhere(students, GetAgeThan);
Console.WriteLine("查询到条数:" + newList1.Count);
//找出邮箱长度大于10位的学生
List<Student> newList2 = StudentWhere(students, GetEmailLen);
Console.WriteLine("查询到条数:" + newList2.Count);
//找出年龄在20岁以上并且邮箱长度大于10位的学生
List<Student> newList3 = StudentWhere(students, GetAgeThanAndEmailLen);
Console.WriteLine("查询到条数:" + newList3.Count);
多播委托
包含多个方法的委托成为多播委托,调用多播委托,可以按照顺序连续调用多个方法。
多播委托可以用运算符"+"和"+="给委托添加方法调用,同样也可以用运算符"-"和"-="给委托删除方法调用
//多播委托
Student s = new Student();
Action action = s.Study;//初始化并添加一个学习的方法
action += s.Sports;//添加一个运动的方法
action();//执行
Console.WriteLine("··············");
action -= s.Sports;//减少一个运动的方法
action();
//输出:
//我正在学习
//我正在运动
//··············
//我正在学习
事件
声明:
public event Action OnSports;//Action 是一个委托
事件:是带event关键字的委托的实例,event可以限制变量被外部调用/直接赋值
事件与委托的区别:
- 委托是一个Class(如Student)
- 而事件是一个委托的实例(如:new Student())
事件与委托实例的区别:
- 事件不能赋值(=)操作,而委托实例可以。
- 事件不能直接调用,如:s.OnSports(),而委托实例可以。
- 事件时一个特殊的委托实例。
我们修改下Student代码,为它添加一个委托的实例对象,一个事件来比较两者的区别:
public class Student
{
public Action OnStudy;//定义了一个委托的实例
public event Action OnSports;//定义了一个事件
public string Name { get; set; }
public int Age { get; set; }
public string Email { get; set; }
public Student(string name, int age, string email)
{
this.Name = name;
this.Age = age;
this.Email = email;
}
public Student() { }
public void Study()
{
if (this.OnStudy != null)
{
this.OnStudy();//调用委托实例
}
Console.WriteLine("我正在学习");
}
public void Sports()
{
if (this.OnSports != null)
{
this.OnSports();//调用事件实例
}
Console.WriteLine("我正在运动");
}
}
在Student外边添加下面代码:
Student s = new Student();
s.OnSports = null;//编译错误,提示:事件“Student.OnSports”只能出现在 += 或 -= 的左边(从类型“Student”中使用时除外)
s.OnSports();//编译错误,提示:事件“Student.OnSports”只能出现在 += 或 -= 的左边(从类型“Student”中使用时除外)
s.OnStudy = null;//正常运行
s.OnStudy();//正常运行