委托的定义和使用

如果我们要把方法当做参数来传递的话,就要用到委托。简单来说委托是一个类型,这个类型可以赋值一个方法的引用。

声明委托

使用delegate关键字定义委托,告诉编译器这个委托可以指向哪些类型的方法,然后,创建该委托的实例。

定义委托:

1
delegate void IntMethodInvoker(int x);

定义了一个委托叫做IntMethodInvoker,这个委托指向以int类型为参数,返回值为void的方法。delegate相当于class,IntMethodInvoker相当于class名,也就是说,定义了委托是需要实例的

其他案例:

1
2
delegate double TwoLongOp(long first,long second);
delegate string GetAString();

使用委托

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
namespace DelegatesTest
{
class DelegateTest1
{
delegate string MyStringMethod();
static void Main(string[] args)
{
//委托可以用new来实例化
MyStringMethod myStringMethod1 = new MyStringMethod(MyStringFunction);
int x = 10;
//委托也可以直接用方法名省去new,这里TOString就是返回值为string的无参函数
MyStringMethod myStringMethod = x.ToString;
Console.WriteLine(myStringMethod());//必须要写括号
Console.WriteLine(myStringMethod1.Invoke());

}
static string MyStringFunction()
{
return "888";
}
}
}

使用delegate.invoke方法来访问被委托的方法,这个方法是在CLR中调用的,不能直接查看元数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class DelegateTest1
{
delegate string MyStringMethod();
static void Main(string[] args)
{

int x = 10;
//委托也可以直接用方法名省去new,这里TOString就是返回值为string的无参函数
MyStringMethod myStringMethod = x.ToString;
Console.WriteLine(myStringMethod.Invoke());//不用写括号使用.invoke方法
}
static string MyStringFunction()
{
return "888";
}
}

委托类型作为参数进行传递

委托delegate相当于class,它声明的对象当然可以作为参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class DelegateTest1
{
delegate string MyStringMethod();
static void Main(string[] args)
{
MyPrintMethod(MyMethod1);
MyPrintMethod(MyMethod2);
}
static void MyPrintMethod(MyStringMethod method)
{
Console.WriteLine( method());
}
static string MyMethod1()
{
return "Method1";
}
static string MyMethod2()
{
return "Method2";
}
}

Action委托与Func委托

由于很多方法的形式都是重复的,比如一种只有一个参数,返回void的方法,我们总不能每种形式的方法都声明一个delegate。所以C#配合泛型,内置了Action委托和Func委托,更加常用。

Action委托代表一个返回void的方法,T表示方法的参数类型,最多能有16个参数。

1
2
3
4
Action
Action<in T>
Action<in T,in T2>
Action<in T,in T2… in T16>

in关键字表示Action委托的参数是可逆变的,比如我们定义一个:

1
2
3
4
5
6
7
8
9
Action<int> intAction;

public void ObjectMethod(object o)
{
//在此方法中,我们只使用object类型的参数,所以传进来int也没问题
}

intAction += ObjectMethod;
intAction?.Invoke(1);

本来是定义的是具体类型参数,但是能够接受其父类基类作为参数的方法,这就是逆变。

Func委托代表一个返回TResult的方法,T表示方法的参数类型,最多能有16个参数。

1
2
3
Func<out TResult>
Func<in T,out TResult>
Func<in T1,in T2,……,in T16,out TResult>

out关键字表示Func委托的参数是可协变的

1
2
3
4
5
6
7
8
Func<int,object> intFunc;
public int ObjectMethod(object o)
{
//在此方法中,我们只使用object类型的参数,所以传进来int也没问题
//在此方法中,我们返回int类型的参数,但Func应用的是object,所以也没问题
}
intFunc += ObjectMethod;
var mType = intFunc?.Invoke(1).GetType();

带有参数的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
class ActionTest1
{
static void Main(string[] args)
{
Action myAction = ForAction;
myAction();

Action<int> myAction2 = ForAction;
myAction2(2);

Action<int, string> myAction3 = ForAction;
myAction3(4, "5");
}

static void ForAction()
{
Console.WriteLine("Action预置委托代表的是无返回函数");
}

static void ForAction(int a)
{
Console.WriteLine(a);
}

static void ForAction(int b,string c)
{
Console.WriteLine(b + " " + c);
}
}

Func委托
Func后面可以跟很多类型,最后一个类型是返回值类型,前面的类型是参数类型,参数类型必须跟指向的方法的参数类型按照顺序对应。

Func后面必须指定一个返回值类型,参数类型可以有0-16个,先写参数类型,最后一个是返回值类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class FuncTest1
{
static string ForFunc(int num1,int num2)
{
int res = num1 + num2;
return res.ToString();
}
static void Main(string[] args)
{
Func<int, int, string> myFunc = ForFunc;

Console.WriteLine( myFunc(2, 3));
}
}

多播委托(multiicast)

委托可以包含多个方法,这种委托叫做多播委托。使用多播委托就可以按照顺序调用多个方法,多播委托只能得到调用的最后一个方法的结果,所以大部分情况下我们使用多播委托都是返回void。

多播委托包含一个逐个调用委托的集合,如果通过委托调用的其中一个方法抛出异常,整个迭代就会停止。

取得多播委托中所有方法

1
2
3
4
5
6
7
8
Action a1 = Method1;
a1 += Method2;
Delegate[] delegates = a1.GetInvocationList();
foreach(delegate d in delegates){
//d();
d.DynamicInvoke(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
namespace MulticastDelegate
{
class MulticastDelegateTest1
{
static string myMethod1()
{
Console.WriteLine("MyMethod1 Launched");
return "get1";
}
static string myMethod2()
{
Console.WriteLine("MyMethod2 Launched");
return "get2";
}
static void Main(string[] args)
{
Func<string> myFunc = myMethod1;
myFunc += myMethod2;//确定myFunc包含的函数个数后,先按顺序执行一遍函数
Console.WriteLine(myFunc());//返回最后一个函数的返回值,但是注意,第一个函数的返回值是有的,只是被覆盖掉了
}
}
}
//output
//MyMethod1 Launched
//MyMethod2 Launched
//get2

使用GetInvocationList配合foreach(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
namespace MulticastDelegate
{
class MulticastDelegateTest1
{
static string myMethod1(string sotext)
{
Console.WriteLine("MyMethod1 Launched");
return sotext;
}
static string myMethod2(string sotext1)
{
Console.WriteLine("MyMethod2 Launched");
return sotext1;
}
static void Main(string[] args)
{
Func<string> myFunc = myMethod1;//当一个委托没有指向任何方法的时候,调用的话会出现异常null
myFunc += myMethod2;//确定myFunc包含的函数个数后,先按顺序执行一遍函数
Delegate[] myDelegates = myFunc.GetInvocationList();
foreach( Func<string,string> f in myDelegates)
{
Console.WriteLine(f.Invoke("Calling"));
}
}
}
}
//output
//MyMethod1 Launched
//Calling
//MyMethod2 Launched
//Calling

匿名方法 Anonymous Methods

到目前为止,使用委托都是先定义一个方法,然后把方法给委托的实例。但还有另外一种使用委托的方式,不用给方法起名了,即使用匿名方法

1
2
3
4
5
6
7
8
Func<int,int,int> plus = delegate(int a,int b){
int temp = a + b;
return temp;
};
int res = plus(34,34);
Console.WriteLine(res);
//在这里使用的delegate关键字,声明的不是方法的类型,而是指向一个具体的方法,delegate在这里指的是这个方法是匿名的。
//匿名方法的出现能够减少要编写的代码,降低复杂性

方法👉Action、Func👉Delegate。直接用委托代替方法,方法名字隐去

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
namespace AnonymousMethod
{
class AnonymousTest1
{
static void Main(string[] args)
{
Func<int, int, int> plus = delegate (int a, int b)//delegate int someMethod (int a,int b)
{
//匿名方法本质上是一个方法,只是没有名字
//任何使用委托变量的地方都可以使用匿名方法赋值
return a + b;
};//需要用分号结尾

Console.WriteLine(plus(2, 3));
}
}
}

Lambda Expression

从C#3.0开始,可以使用Lambda表达式代替匿名方法,只要有委托参数类型的地方就可以使用Lambda表达式。匿名方法可以修改为

1
2
3
4
Func<int,int,int> plus = (a,b) => {int temp = a + b; return temp;};
int res = plus(34,34);
Console.WriteLine(res);
Lambda运算符“=>”的左边列出了需要的参数,如果是一个参数可以直接写 a =>(参数名自己定义),如果多个参数就使用括号括起来,参数之间以逗号间隔。

进一步简化匿名方法的书写,只需要列出参数名字,然后写明参数的操作

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
namespace LambdaExpression
{
static void Main(string[] args)
{
Func<int,int,int> plus = (a,b) => {return a + b;};
Func<int,int,int> plus2 = (a,b) => a + b;//只有一行时,省略return和大括号
Func<int,int> plus3 = a => a + 1;//只有一行时,省略参数的括号
Func<int,int> plus4 = a =>
{
return a * a;
};
Action<int> myAction = a => Console.WriteLine(a);

Console.WriteLine(plus(1,2));
Console.WriteLine(plus2(3,4));
Console.WriteLine(plus3(3));
Console.WriteLine(plus4(3));
myAction(9);
}
}
//output:
//3
//7
//4
//9
//9

多行语句

  1. 如果Lambda表达式只有一条语句,在方法块内就不需要花括号和return语句,编译器会自动添加return语句
1
2
3
4
5
Func<double,double> square = x => x*x;
//添加花括号,return语句和分号是完全合法的
Func<double,double> square = x =>{
return x * x;
}
  1. 如果Lambda表达式的实现代码中需要多条语句,就必须添加花括号和return语句。

Lambda表达式外部变量

通过Lambda表达式可以访问Lambda表达式块外部的变量。这是一个非常好的功能,但如果不能正确使用也会非常危险。示例:

1
2
3
4
5
6
int somVal = 5;
Func<int,int> f = x => x + somVal;
Console.WriteLine(f(3));//8
somVal = 7;
Console.WriteLine(f(3));//10
//这个方法的结果,不但受到参数的控制,还受到somVal变量的控制,结果不可控,容易出现编程问题,用的时候要谨慎