CS4L12——委托

本章代码关键字

1
2
3
4
delegate    //委托声明关键字
Action<> //C#自带的无返回值委托
Func<> //C#自带的有返回值委托
Invoke() //委托可以使用此方法来执行存储于其中的方法

委托

委托是函数(方法)的容器,可以理解为表示特定格式(参数列表和返回值类型一致)的函数(方法)的变量类型,用来存储传递函数(方法)
委托的本质是一个类,用来定义函数(方法)的类型(返回值和参数的类型),
不同的函数(方法)必须对应和各自 “格式“ 一致(即参数列表和返回值类型必须一致)的委托

简单理解 委托 就是装载、传递函数的容器而已,可以使用委托变量来 存储函数 或者 传递函数
系统已经提供了很多委托给我们用:

  • Action​:没有返回值,参数提供了 0-16 个委托给我们用
  • Func​: 有返回值,参数提供了 0-16 个委托给我们用

委托可以让我们接收外部传入的函数,然后一并执行,这在UI系统中的为按钮等控件定义触发的行为时将非常有用

基本语法

关键字:delegate
语法:访问修饰符 delegete 返回值 委托名(参数列表);

写在哪里:
可以申明在 namespace​ 和 class​ 语句块中
更多的写在 namespace​ 中

委托语法简单记忆就是:函数申明语法前面加一个 delegate​ 关键字

定义自定义委托

委托的 访问修饰符 一般不写,默认是 public​,即委托在别的命名空间中也能使用
使用 private​ 会让其他命名空间不能使用了,因此一般用 public

委托声明不能同名(在同一语句块中)

声明委托只是申明了规则(参数列表和返回值类型),并没有使用
委托可以使用泛型,可以让返回值和参数类型可变 方便我们的使用

1
2
3
public delegate void MyFun();                //委托规则的申明 是不能同名的(在同一语句块中)
public delegate int MyFun2(int a); //表示用来转载或传递 返回值为int 有一个int参数的函数的 委托容器规则
public delegate T MyFun3<T, K>(T t, K k); //委托可以使用泛型,可以让返回值和参数类型可变 方便我们的使用

调用委托以执行委托存储的函数

委托是专门用来装载 函数 的容器,可以 new​ 委托来声明一个空委托,或者 new​ 委托时传入一个函数,也可以直接赋值一个函数
要执行委托存储的函数,可以对委托调用 Invoke()​ 方法,也可以如方法那样直接 委托变量()

函数不能装在不匹配格式的委托里,例如有参函数就不能存储在无参委托内

注意:向委托装载一个函数时,函数不能加括号,如果加上括号就是调用该函数了

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
public delegate void MyFun();

static void Main(string[] args)
{
//第一种装载方法
MyFun f = new MyFun(Fun);
// 可以执行一堆逻辑后再执行委托
Console.WriteLine("1");
Console.WriteLine("2");
Console.WriteLine("3");
f.Invoke(); //第一种调用方法 委托变量.Invoke();
// 第二种装载方法
MyFun f2 = Fun;
f2(); //第二种调用方法 委托变量();

//函数不能装在不匹配格式的委托里
//MyFun f3 = Fun2;
//函数只能装在格式一致的委托里
MyFun2 f3 = Fun2;
Console.WriteLine(f3(1));

MyFun2 f4 = new MyFun2(Fun2);
Console.WriteLine(f4.Invoke(3));
}

static void Fun()
{
Console.WriteLine("我做了什么");
}

static int Fun2(int value)
{
return value;
}

输出:

1
2
3
4
5
6
7
1
2
3
我做了什么
我做了什么
1
3

使用定义好的委托

委托类型变量是函数的容器,常用在:

  1. 作为类的成员
  2. 作为函数的参数
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
public delegate void MyFun();                //委托规则的申明 是不能同名的(在同一语句块中)
public delegate int MyFun2(int a); //表示用来转载或传递 返回值为int 有一个int参数的函数的 委托容器规则
public delegate T MyFun3<T, K>(T t, K k); //委托可以使用泛型,可以让返回值和参数类型可变 方便我们的使用

class Test
{
public MyFun fun;
public MyFun2 fun2;

public void TestFun(MyFun fun, MyFun2 fun2)
{
//可以先处理一些别的逻辑,当这些逻辑处理完了 在执行传入的函数
int i = 1;
i *= 2;
i += 2;

fun();
fun2(i);

//可以让传入的委托 保存在成员变量里
this.fun = fun;
this.fun2 = fun2;
}
}

internal class Program
{
static void Main(string[] args)
{
//注意:被装载的方法不能加括号,否则就是调用该函数了, 除非该函数返回值是委托变量
Test t = new Test();
t.TestFun(Fun, Fun2);
}

static void Fun()
{
Console.WriteLine("我做了什么");
}

static int Fun2(int value)
{
return value;
}
}

输出:

1
2
我做了什么
4

委托变量可以存储多个函数(多播委托)

所谓多播委托,即 “多路广播委托”(MulticastDelegate)。
从它的名字就可以看出,此种委托可以像广播一样将影响信息“传播”到四面八方。
多播委托类拥有一个方法调用列表,调用委托时,它就会逐一调用该列表中的方法,从而实现多重影响。

简单来说就是,委托可以存储多个函数,调用委托时,会依次执行存储的所有函数

可以使用 +=​ 向委托添加多个函数,
但是要注意,没有初始化的委托是不能直接加函数的!!! ,也就是说,不能刚声明一个委托变量时就使用 +=
MyFun ff = null​ 可以算是初始化,可以在后续语句里用 +=
当委托有多个函数时,会按照添加的先后顺序依次执行添加的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public delegate void MyFun();

static void Main(string[] args)
{
//注意不能直接写MyFun ff += Fun; 没有初始化是不能直接加的!!!
MyFun ff = Fun;
//用 += 即可 等同于ff = ff + Fun3;
ff += Fun3;
ff();
}

static void Fun()
{
Console.WriteLine("我做了什么");
}

static void Fun3()
{
Console.WriteLine("你做了什么");
}

输出:

1
2
我做了什么
你做了什么

使用 -=​ 可以从容器中移除指定的函数,如果容器本来就没有这个函数,再去移除该函数会不进行处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public delegate void MyFun();

static void Main(string[] args)
{
MyFun ff = Fun;
ff += Fun3;
ff();
Console.WriteLine("————————分割线————————");

ff -= Fun3;
ff();
ff -= Fun3;
//如果容器本来就没有这个函数,再去移除该函数会不进行处理
}

static void Fun()
{
Console.WriteLine("我做了什么");
}

static void Fun3()
{
Console.WriteLine("你做了什么");
}

输出:

1
2
3
4
我做了什么
你做了什么
————————分割线————————
我做了什么

对委托直接赋值 null​ 即可清空委托

注意!如果委托是 null​,直接调用会报错!!!,建议用 if​ 判断来确保不会报错

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
public delegate void MyFun();

static void Main(string[] args)
{
MyFun ff = Fun;
ff += Fun3;

//如果委托是null,直接调用会报错!!!,建议用if判断来确保不会报错
if (ff != null)
{
ff();
}
else
{
Console.WriteLine("ff没有存储方法");
}
}

static void Fun()
{
Console.WriteLine("我做了什么");
}

static void Fun3()
{
Console.WriteLine("你做了什么");
}

输出:

1
ff没有存储方法

如果一个委托要判断确认不为 null​ 后才执行,除了使用 if​ 判断,
还可以使用 ?.Invoke()​ 的方法来调用委托,委托在不等于 null​ 时才会调用
这个 ?.​ 是 null​ 检查运算符,它用于简化判空逻辑,会在(#TODO#​)内详细讲解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public delegate void MyFun();

static void Main(string[] args)
{
MyFun ff = null;

//如果委托是null,直接调用会报错!!!,建议用if判断来确保不会报错
if (ff != null)
{
ff();
}
// 下面的写法等价于上面的写法
ff?.Invoke();
}

我们可以封装方法来为类内部的委托变量添加函数或移除函数
在类里为已经有的委托成员变量,在函数里直接使用 +=​ 是可以的,因为实例化类时已经默认进行了初始化(赋值为 null​)

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
public delegate void MyFun();
public delegate int MyFun2(int a);

class Test
{
public MyFun fun;
public MyFun2 fun2;

public void AddFun(MyFun fun, MyFun2 fun2)
{
//在类里 为已经有的委托成员变量 在函数里直接 使用 += 是可以的,因为实例化类时已经进行了初始化(=null)
this.fun += fun;
this.fun2 = fun2;
}

public void RemoveFun(MyFun fun, MyFun2 fun2)
{
//等同于this.fun = this.fun - fun;
this.fun -= fun;
this.fun2 -= fun2;
}
}

internal class Program
{
static void Main(string[] args)
{
Test t = new Test();
//可以调用实例化对象里存储的委托成员变量,实例化对象.委托变量() 即可
t.AddFun(Fun, Fun2);
t.fun();
t.fun2(50);

t.RemoveFun(Fun, Fun2);
t.fun?.Invoke(); //这里要进行空检查,如果不为空才执行,否则可能报错
t.fun2?.Invoke(50); //这里要进行空检查,如果不为空才执行,否则可能报错
}
}

输出:

1
2
我做了什么
50

系统定义好的委托

可以直接使用系统提供的常用委托,需要 using System

  • Action<>​ 是代表无返回值委托,系统提供了1-16个参数的 Action​ 委托,直接用即可,类型可以分别指定

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    static void Main(string[] args)
    {
    //Action代表无返回值委托
    Action action = Fun;
    action += Fun3;
    action();

    //Action 代表无返回值委托 系统提供了1-16个参数的Action委托,直接用即可,类型可以分别指定
    Action<int, String> action2 = Fun6;
    }

    static void Fun()
    {
    Console.WriteLine("我做了什么");
    }

    static void Fun3()
    {
    Console.WriteLine("你做了什么");
    }

    static void Fun6(int i, string s) { }

    输出:

    1
    2
    我做了什么
    你做了什么
  • Func<>​ 是代表有返回值委托,系统提供了1-16个参数的 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
    static void Main(string[] args)
    {
    //Func代表可以指定返回值委托
    Func<string> funcString = Fun4;
    Func<int> funcInt = Fun5;

    //Func 代表可以指定返回值的委托 系统提供了1-16个参数的Func委托,直接用即可
    //注意!指定类型时,参数的类型写在前面,最后一个一定是返回值的类型
    Func<int, int> func2 = Fun2;
    }

    static int Fun2(int value)
    {
    return value;
    }

    static string Fun4()
    {
    return "";
    }

    static int Fun5()
    {
    return 0;
    }