0%

真会C#?——委托

委托简介

什么是委托

  • C#的委托(Delegate)类似于 C 或 C++ 中函数的指针。它是一个对象,他知道该如何调用一个方法,其实它就是一个方法的包装器或引用变量。

定义委托

  • 委托类型:委托类型定义了委托实例可以调用的那类方法,即定义了方法的返回类型和参数类列表
  • 所有的委托类型都派生于System.MuticastDelegate,而它又派生于System.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
class Program
{
/**
* 委托类型:使用delegate关键字进行定义
* 这里定义了委托可以调用的方法的返回类型和参数列表
* delegate [返回类型] [委托类型名称(跟类名一样)]([参数列表]);
*/
delegate int Transformer(int x);
static void Main(string[] args)
{
/**
* 委托实例:用方法的引用来实例化一个Transformer类型的委托
* 这个其实跟类的实例化没什么差别,就是用来实例化委托的对象是方法的引用
* [委托名称] t = new [委托名称]([方法名称]);这时t委托实例就拥有了Square方法调用的权力
* 简写:[委托名称] t = [方法名称];
*/
Transformer t = Square;
/**
* 使用委托实例来调用方法,这跟Square(3)的效果是一样的
* [委托实例].Invoke([蚕食列表]);
* 简写:[委托实例]([蚕食列表]);
*/
var result = t(3);
Console.WriteLine(result);
}

static int Square(int x) => x * x;
}

委托的好处

  • 委托实例其实就是调用者的委托:调用者调用委托,再由委托调用目标方法。这样做的好处就是把调用者和目标方法解耦合了,使用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
class Program
{
delegate int Transformer(int x);

class Util
{
public static void Transform(int[] values, Transformer t)
{
for (int i = 0; i < values.Length; i++)
{
values[i] = t(values[i]);
}
}
}
static void Main(string[] args)
{
DelegateDemo();
}
static int Square(int x) => x * x;

static void DelegateDemo()
{
int[] values = {1, 2, 3};
Util.Transform(values, Square);
foreach (var value in values)
{
Console.WriteLine($"{value}");
}
}
}

多播委托

  • 一个委托实例可以引用一组目标方法,所有的委托实例都具有多播的功能。
  • 使用++=操作符就可以合并委托实例,合并后调用就会顺序地调用各个委托实例委托的方法,调用的顺序跟合并的顺序一致。
  • 使用--=操作访可以移除合并的委托实例。
  • C#会把这些操作符编译成System.Delegate中的CombineRemove两个静态方法
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
class Program
{
public delegate int Transformer(int x);
static void Main(string[] args)
{
Transformer t = null;
//在这里方法也可以当成是一个委托实例
//Transformer t = Square
t += Square;
//t = t + Cube
t += Cube;

//调用的时候就会按照Square、Cube顺序进行调用
//返回最后一个方法的返回值
var i = t(3);
Console.WriteLine(i);

//移除委托实例
//若把所有的委托实例都移除了,那么委托实例会为null
t -= Cube;

var j = t(3);
Console.WriteLine(j);

}

static int Square(int x)
{
var result = x * x;
Console.WriteLine(result);
return result;
}

static int Cube(int x)
{
var result = x * x * x;
Console.WriteLine(result);
return result;
}
}

实例方法目标和静态方法目标

  • 当一个实例方法被赋值给委托对象的时候,这个委托对象不仅要保留着对方法的引用,还要保留着方法所属实例的引用
  • 而实例的引用就是System.Delegate中的Target属性。
  • 如果引用的是静态方法,那么Target属性的值就为null
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Program
{
delegate void ProgressReporter(int x);
class X
{
public void InstInstanceProgress(int x) => Console.WriteLine($"X:{x}");
}
static void Main(string[] args)
{
X x = new X();
/**
* 当p委托了x对象中的InstInstanceProgress方法时,
* 他也会保留方法所属的x对象
*/
ProgressReporter p = x.InstInstanceProgress;
p(99);
//True
Console.WriteLine(p.Target == x);
//Void InstInstanceProgress(Int32)
Console.WriteLine(p.Method);
}
}

泛型委托类型

  • 委托类型可以包含泛型类型的参数,public delegate T Transformer<T> (T arg);
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
class Program
{
delegate T Transformer<T>(T arg);

class Util
{
public static void Transform<T>(T[] values, Transformer<T> t)
{
for (int i = 0; i < values.Length; i++)
{
//t(values[i])只返回Cube的返回值
values[i] = t(values[i]);
}
}
}
static void Main(string[] args)
{
int[] values = {1, 2, 3};
Transformer<int> t = Square;
t += Cube;
Util.Transform(values, t);

foreach (var value in values)
{
Console.WriteLine(value);
}
}

static int Square(int x) => x * x;
static int Cube(int x) => x * x * x;
}

Func和Action委托

Func

  • Func类型的定义都放在了System命名空间下,它代表的是有返回值的委托类型,它有多种不同的泛型类型可供使用:
    1. delegate TResult Func<out TResult>();:有返回值,没有参数的委托类型,out表示TResult类型的变量只能作为方法的输出。
    2. delegate TResult Func<in T, out TResult>(T arg);:有返回值,一个参数的委托类型,in表示T类型的变量只能作为方法的输入。
    3. delegate TResult Func<in T1, in T2... out TResult>(T1 arg1, T2 arg2...);:有返回值,多个个参数的委托类型,最多可以支持16个输入参数。

Action

  • Action类型的定义都放在了System命名空间下,它代表的是没有返回值的委托类型,它有多种不同的泛型类型可供使用:
  1. delegate void Action();:没有返回值,没有参数的委托类型。
  2. delegate void Action<in T>(T arg);:没有返回值,一个参数的委托类型。
  3. delegate void Action<in T1, in T2...>(T1 arg1, T2 arg2...);:没有返回值,多个个参数的委托类型,最多可以支持16个输入参数。
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
class Program
{
static void Main(string[] args)
{
int[] values1 = {1, 2, 3};

//使用Func委托类型进行委托
Transform1(values1, Square);
foreach (var value in values1)
{
Console.WriteLine(value);
}

//使用Action委托类型进行委托
int[] values2 = { 1, 2, 3 };
Transform2(values2, Cube);
}

static void Transform1<T>(T[] values, Func<T, T> tranFunc)
{
for (int i = 0; i < values.Length; i++)
{
//Func有返回值
values[i] = tranFunc(values[i]);
}
}

static void Transform2<T>(T[] values, Action<T> tranAction)
{
for (int i = 0; i < values.Length; i++)
{
//Action没有返回值
tranAction(values[i]);
}
}

static int Square(int x) => x * x;

static void Cube(int x) => Console.WriteLine(x*x*x);
}

接口和委托

  • 委托可以解决的委托,接口都可以解决,那在什么时候更适合自定义的委托而不是使用接口呢?
    1. 需要多播的功能,接口不能实现多播的功能
    2. 订阅者需要多次实现接口jian

委托的兼容性

  • 委托类型:委托类型之间时互不相容的,即使方法签名一样。
  • 委托实例:如果委托实例拥有相同的方法目标,那么委托实例就是相等的。
  • 参数:委托可以接受比它的定义参数类型更加抽象的参数类型的方法,这个叫做委托的逆变。
  • 返回值:委托可以接受比它的定义返回类型更加具体的返回类型的方法,这个叫做委托的协变。
1
2
3
4
5
6
7
8
9
10
11
12
13
class Program
{
//委托的参数类型是string
private delegate object Printer(string str);
static void Main(string[] args)
{
Printer printer = Print;
printer("Hello World");
}

//而目标方法的参数是object,比委托参数更加抽象
static string Print(object o) => (string)o;
}
  • 而委托支持的仅仅只是引用转换,而不支持装箱拆箱操作,比如下面例子:他并不支持将int进行装箱
1
2
3
4
5
6
7
8
9
class Program
{
private delegate void StringAction(int str);
static void Main(string[] args)
{
StringAction stringAction = Print;
}
static void Print(object o) => Console.WriteLine(o);
}

学习资料:B站杨旭

-------------本文结束感谢您的阅读-------------