委托的定义和使用 如果我们要把方法当做参数来传递的话,就要用到委托。简单来说委托是一个类型,这个类型可以赋值一个方法的引用。
声明委托 使用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 ) { MyStringMethod myStringMethod1 = new MyStringMethod(MyStringFunction); int x = 10 ; 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 ; MyStringMethod myStringMethod = x.ToString; Console.WriteLine(myStringMethod.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 ){ } 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 ){ } 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.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; Console.WriteLine(myFunc()); } } }
使用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; myFunc += myMethod2; Delegate[] myDelegates = myFunc.GetInvocationList(); foreach ( Func<string ,string > f in myDelegates) { Console.WriteLine(f.Invoke("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);
方法👉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) { 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; 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 ); } }
多行语句
如果Lambda表达式只有一条语句,在方法块内就不需要花括号和return语句,编译器会自动添加return语句
1 2 3 4 5 Func<double ,double > square = x => x*x; Func<double ,double > square = x =>{ return x * x; }
如果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 )); somVal = 7 ; Console.WriteLine(f(3 ));