前言
整理归纳一下他俩的联系和区别。
一、联系
Action 和 Func 都是 .Net 预定义的 委托类型。
二、区别
看一下 Action 的定义,有两类:
public delegate void Action(); public delegate void Action<in T>(T obj); public delegate void Action<in T1, in T2>(T1 arg1, T2 arg2); public delegate void Action<in T1, in T2, in T3>(T1 arg1, T2 arg2, T3 arg3); ...
再看一下 Func 的定义,也有两类:
public delegate TResult Func<out TResult>(); public delegate TResult Func<in T, out TResult>(T arg); public delegate TResult Func<in T1, in T2, out TResult>(T1 arg1, T2 arg2); public delegate TResult Func<in T1, in T2, in T3, out TResult>(T1 arg1, T2 arg2, T3 arg3); ...
可以清楚的看到:
1,Action 没有返回值, Func 有返回值。
2,Func 参数列表里最后一个,是该委托方法的返回值类型。(通过定义里的 in 和 out 可以分辨)
三、代码示例
下面通过代码,演示他们的用法和区别。
Action
1,最简单的情况,不带参数的 Action(),同步执行:
class Program { static void Main(string[] args) { Action action = Printf_done; //写法1 action(); //写法2 action.Invoke(); //加不加 Invoke() 有什么区别吗 //写法2 Printf(); Console.ReadKey(); } static void Printf_done() { Console.WriteLine("done"); } }
上面 3 种写法的结果都打印出 “done”,写法 1 和写法 2 最后生成的 IL 指令是一样的,可见 action() 是 action.Invoke() 的简写。
2,不带参数的 Action<in T>(T obj) ,异步执行,需要回调函数,回调必须带参数:
class Program { static void Main(string[] args) { Action action = Printf_done; action.Invoke(); action.BeginInvoke(Printf_CallBack, "优蓝盛夏"); Console.WriteLine("Main 结束, 线程ID:{0}", Thread.CurrentThread.ManagedThreadId); Console.ReadKey(); } static void Printf_done() { Console.WriteLine("done, 线程ID:{0}",Thread.CurrentThread.ManagedThreadId); } //AsyncCallback 类型的方法 static void Printf_CallBack(IAsyncResult async) { Thread.Sleep(2000); Console.WriteLine("{0} done, 线程ID:{1}", async.AsyncState, Thread.CurrentThread.ManagedThreadId); } }
执行结果:
可以看到,Invoke 是同步的,BeginInvoke 是异步的。BeginInvoke 异步的时候,必须指定一个 AsyncCallback 类型的方法,这个方法很简单,包含一个 IAsyncResult 类型的参数就可以了。
3,带参数的 Action<in T>(T obj) ,同步执行和异步执行对比:
class Program { static void Main(string[] args) { Action<string> action = Printf_done; action.Invoke("同步 action"); //与 action("action");有什么区别吗? action.BeginInvoke("异步 action",Printf_CallBack, "优蓝盛夏"); Console.WriteLine("Main 结束, 线程ID:{0}", Thread.CurrentThread.ManagedThreadId); Console.ReadKey(); } static void Printf_done(string name) { Console.WriteLine("{0} done, 线程ID:{1}", name,Thread.CurrentThread.ManagedThreadId); } //AsyncCallback 类型的方法 static void Printf_CallBack(IAsyncResult async) { Thread.Sleep(3000); Console.WriteLine("{0}, 线程ID:{1}", async.AsyncState, Thread.CurrentThread.ManagedThreadId); } }
执行结果:
可以看出,带参数的时候, Invoke 和 BeginInvoke 的参数列表都要带有实参,BeginInvoke 的时候,和回调函数写在一起。
4,匿名方法,这里穿插一个匿名方法的 “发展史”:
class Program { static void Main(string[] args) { //写法 1 Action<string> action = (string name) => { Console.WriteLine("{0} done, 线程ID:{1}", name, Thread.CurrentThread.ManagedThreadId); }; //写法2 Action<string> action = (name) => { Console.WriteLine("{0} done, 线程ID:{1}", name, Thread.CurrentThread.ManagedThreadId); }; //写法3 Action<string> action = name => { Console.WriteLine("{0} done, 线程ID:{1}", name, Thread.CurrentThread.ManagedThreadId); }; action.Invoke("同步 action"); action.BeginInvoke("异步 action", Printf_CallBack, "优蓝盛夏"); Console.WriteLine("Main 结束, 线程ID:{0}", Thread.CurrentThread.ManagedThreadId); Console.ReadKey(); } //AsyncCallback 类型的方法 static void Printf_CallBack(IAsyncResult async) { Thread.Sleep(3000); Console.WriteLine("{0}, 线程ID:{1}", async.AsyncState, Thread.CurrentThread.ManagedThreadId); } }
写法 1 、2、3 的运行结果都一样,但是写法却 “笑容逐渐变态”。
写法 1 ,匿名方法把返回值类型匿了,因为可以编译器推断(Action 肯定为 void);把方法名匿了,因为编译器可以自己取个名。
写法 2,青出于蓝,把参数类型都匿了,因为编译器可以根据方法体进行推断(Action<string> 已经指定了类型)。
写法 3,胜于蓝,把参数列表的括号都省略了。注意:当没有参数和有多个参数的时候,括号可是要保留的哦。
Func
这里直接写匿名方法,示例同步情况下无参和有两个参的情况,返回值是必须要有的。异步的情况和 Action 一样,不赘述了:
class Program { static void Main(string[] args) { //没有参数,但是有返回值 Func<string> func = () => { return "Func"; }; Console.WriteLine(func.Invoke() + " done, 线程ID:{0}", Thread.CurrentThread.ManagedThreadId); //两个参数,但是有返回值 Func<int, int, string> func = (x, y) => { Console.Write("Func "); return (x + y).ToString(); }; Console.WriteLine(func.Invoke(2, 7) + " done, 线程ID:{0}", Thread.CurrentThread.ManagedThreadId); Console.ReadKey(); } }
执行结果:
可以看出,Action 和 Func 除了有无返回值之外,没有任何区别。他们的重载也都是最多有16个参数。如果想要有多个返回值,可以使用委托自己定义,返回类型用 tuple 或者 Dictionary 或者 List 都可以。
后记
异步操作的时候,使用了回调函数,实际上也必须使用回调,不然无法异步,为什么这么说,其实这是语法的关系
举个例子,下面这句代码:
action.BeginInvoke("异步 action", Printf_CallBack, "优蓝盛夏");
Printf_CallBack 是方法名,”优蓝盛夏” 是 Printf_CallBack 的参数。那么问题来了,为什么 BeginInvoke 的参数列表,要把方法名和方法的参数分开写,如果这样写岂不是更好理解:
action.BeginInvoke("异步 action", Printf_CallBack("优蓝盛夏"));
其实这就是个语法问题,Printf_CallBack(“优蓝盛夏”); 表示的是立刻执行 Printf_CallBack 方法并且返回,而我们是要异步啊,我们不知道这个方法会什么时候执行,所以要把方法名和参数拆开丢给 BeginInvoke 。
喜欢砸锅卖铁,哦不,喜欢打破砂锅问到底的话,可以看一下编译后的 IL 代码。