委托
编程中,可能会遇到以下逻辑
需求:
写一个方法,输出数组中满足条件(5的倍数,除7余1……)的数字。
我们可以这样写:
方法1(数字x):如果x是5的倍数,返回真。
方法2(数字x):如果x除以7余1,返回真。
打印1(数组a){if(方法1(数组a中所有的元素))打印该元素}
打印2(数组a){if(方法2(数组a中所有的元素))打印该元素}
主方法酌情调用。代码如下:
1 static bool ff1(int x) 2 { 3 return x % 5 == 0; 4 } 5 static bool ff2(int x) 6 { 7 return x % 7 == 1; 8 } 9 static void dy1(int[] a) 10 { 11 foreach(var t in a) 12 { 13 if(ff1(t)) 14 { 15 Console.WriteLine(t); 16 } 17 } 18 } 19 static void dy2(int[] a) 20 { 21 foreach (var t in a) 22 { 23 if (ff2(t)) 24 { 25 Console.WriteLine(t); 26 } 27 } 28 } 29 static void Main(string[] args) 30 { 31 int[] a = new int[25]; 32 for (int i = 0; i < a.Length; i++) 33 { 34 a[i] = i + 1; 35 } 36 dy1(a); 37 dy2(a); 38 Console.ReadKey(); 39 }
运行结果如图:
打印1和打印2看起来高度相似,让我们有一种简写愿望。如果能写成下面这样就好了:
打印(数组 a,方法 b){if(方法b(数组a中所有的元素))打印该元素}
即把方法也作为参数传递过去,然后综合处理。委托恰好可以实现这个需求。
c#关于委托,不同时期有不同的主流表达方法。这里只介绍现阶段,用起来方便快捷的用法。
委托的实质:定义一个指向(表示)方法的变量,就像int a定义了一个表示整数的变量一样。
知识点:1、Func表示有一个返回值的方法;2、Action表示一个无返回值的方法(即void);3、其他,此处不介绍。
上例中,想完成数据的综合处理,可以写成:
static bool ff1(int x) { return x % 5 == 0; } static bool ff2(int x) { return x % 7 == 1; } static void dy(int[] a,Func<int,bool> b) { foreach (var t in a) { if (b(t)) { Console.WriteLine(t); } } } static void Main(string[] args) { int[] a = new int[25]; for (int i = 0; i < a.Length; i++) { a[i] = i + 1; } dy(a,ff1); dy(a,ff2); Console.ReadKey(); }
运行结果不变。
其中,“Func<int,bool> b”表示b是一个返回值为bool,参数为int的任意方法。即“<>”中最后一个数据类型表示返回值,前面的都是参数类型。
(ps:所有参数加起来不能超过16个,一般够用。Action一样,略。)
日常开发中,合理地使用委托能够更好地让程序员专注于类的内部开发,而把程序的耦合部分作为接口保留到使用的时候,由组合程序员进行拼装,使程序更加条理清晰。
委托的综合例(这是我在网上见到的一个非常好的能说明委托优势,并且包含了多播知识的例子):
平时家用的热水器,很多时候是由不同的热水器主体(能烧水、监测温度)、不同的温度显示器(指针、数字、液晶面板等)、不同的报警器(声、光等)构成。
我们在这里编写一个热水器类,一个温度显示器类,一个报警器类。开发的时候它们专注于自己的功能,互相都可以不知道对方的情况;主程序中按照通用的方式进行组装。
三者的功能划分如下:
热水器类应当有一个温度属性和一个烧水动作。作为一个可以附加温度显示器和报警器的好产品,它还应该在水温改变时,向外界送出当前的温度。
显示器类只负责显示温度,不管接在热水器还是烤箱、空调等任何家电上,只要有温度送进来,它就可以显示。
报警器类只负责在温度超过警戒线时进行报警,接在什么设备上应该都可以运行。
代码如下(为了让程序好看,大量采用中文类名、变量名。实际大项目协同开发中应尽量避免):
热水器类:
1 class 热水器 2 { 3 //默认冷水水温为20度 4 int 温度 =20; 5 6 //定义一个委托,在适当的时候向外送出温度 7 //至于送出温度以后对方怎么处理,热水器类不关心。 8 //由于接收温度的设备可能有多个(例如本例中的显示器和报警器),所以这个委托不方便有返回值(大家都有返回值,你接谁的呢?) 9 public Action<int> 送出温度; 10 11 //模拟热水器的加热动作,每加热一次,水温升高一度 12 public void 加热() 13 { 14 if(温度<100) 15 { 16 温度++; 17 } 18 //本热水器设计为,每执行一次加热动作,向外送出一次温度 19 送出温度(温度); 20 } 21 }
显示器类:
class 显示器 { //很简单,仅显示接收到的温度,不管谁送来的。 public void 显示温度(int 要显示的温度) { Console.WriteLine("我看到了,当前温度是:"+要显示的温度.ToString()); } }
报警器类:
class 报警器 { int 临界温度; public 报警器(int 设定临界温度) { 临界温度 = 设定临界温度; } public void 报警器工作(int 传入温度) { if(传入温度>=临界温度) { Console.WriteLine("滴滴滴~,不得了了,温度太高,已经"+传入温度.ToString()+"度了"); } } }
主程序:
static void Main(string[] args) { //热水器及配件 热水器 我家的热水器 = new 热水器(); 显示器 我家的显示器 = new 显示器(); 报警器 我家的报警器 = new 报警器(80); //组装 //+=运算符实现一个消息传递给多个方法,叫做多播 //因为之前提到的返回值问题,也只有Action委托才可以多播 我家的热水器.送出温度 += 我家的显示器.显示温度; 我家的热水器.送出温度 += 我家的报警器.报警器工作; //运行,烧水120次 for (int i = 1; i <=120; i++) { 我家的热水器.加热(); } Console.ReadKey(); }
运行效果自行查看。
在热水器中,把“送出温度”的委托添加一个关键字“event”就成了事件。
public event Action<int> 送出温度;
整个程序依然能正常运转。
相对于纯委托,使用事件的意义在于:
加了event后只能用”+= -=“来添加或移除处理回调,而”=“不行。这在一定程度上保证了event系统在一个事件触发后感兴趣的系统都会得到通知,而不会被一个错误的赋值把别的已经注册的处理都覆盖掉。
“Lambda 表达式”是采用以下任意一种形式的表达式:
表达式 lambda,表达式为其主体:
(input-parameters) => expression
语句 lambda,语句块作为其主体:
(input-parameters) => { <sequence-of-statements> }
使用 lambda 声明运算符=> 从其主体中分离 lambda 参数列表。 若要创建 Lambda 表达式,需要在 Lambda 运算符左侧指定输入参数(如果有),然后在另一侧输入表达式或语句块。
任何 Lambda 表达式都可以转换为委托类型。 Lambda 表达式可以转换的委托类型由其参数和返回值的类型定义。 如果 lambda 表达式不返回值,则可以将其转换为 Action 委托类型之一;否则,可将其转换为 Func 委托类型之一。 例如,有 2 个参数且不返回值的 Lambda 表达式可转换为 Action<T1,T2> 委托。 有 1 个参数且返回值的 Lambda 表达式可转换为 Func<T,TResult> 委托。
即:“Lambda 表达式”可以看做简写的函数。
所以,如果我们想打印所有3的倍数(没有现成的方法,想用最快的速度临时写一个),上例可以用它改写为:
static void dy(int[] a,Func<int,bool> b) { foreach (var t in a) { if (b(t)) { Console.WriteLine(t); } } } static void Main(string[] args) { int[] a = new int[25]; for (int i = 0; i < a.Length; i++) { a[i] = i + 1; } dy(a,x=>x%3==0); Console.ReadKey(); }
运行效果为:
例:利用兰姆达表达式,在List列表中实现增删改查。(多用于配合EF框架)
用到的Person类:
using System; using System.Collections.Generic; using System.Text; namespace ConsoleApp1 { class Person { public string xm { get; set; } public int nl { get; set; } public static List<Person> getPersons() { List<Person> l = new List<Person>(); Person t; Random random = new Random(); for (int i = 0; i < 25; i++) { t = new Person() { xm = "a" + (i + 1), nl = random.Next(15, 50) }; l.Add(t); } return l; } } }
主程序:
using System; using System.Collections.Generic; using System.Linq; namespace ConsoleApp1 { class Program { static void Main(string[] args) { List<Person> p = Person.getPersons(); printPerson(p); //增:见Person类 //删:18岁以下 p.RemoveAll(x => x.nl < 18); printPerson(p); //改:年龄双数,+2 foreach (var item in p.Where(x=>x.nl%2==0)) { item.nl += 2; } printPerson(p); //查:见“改” } static void printPerson(List<Person> x) { x.ForEach(xx => Console.WriteLine($"name:{xx.xm},age:{xx.nl}")); Console.WriteLine(); } } }
运行效果:
name:a1,age:26 name:a2,age:46 name:a3,age:17 name:a4,age:39 name:a5,age:17 name:a6,age:41 name:a7,age:37 name:a8,age:33 name:a9,age:42 name:a10,age:25 name:a11,age:31 name:a12,age:19 name:a13,age:35 name:a14,age:31 name:a15,age:44 name:a16,age:49 name:a17,age:31 name:a18,age:34 name:a19,age:17 name:a20,age:41 name:a21,age:46 name:a22,age:46 name:a23,age:43 name:a24,age:37 name:a25,age:31 name:a1,age:26 name:a2,age:46 name:a4,age:39 name:a6,age:41 name:a7,age:37 name:a8,age:33 name:a9,age:42 name:a10,age:25 name:a11,age:31 name:a12,age:19 name:a13,age:35 name:a14,age:31 name:a15,age:44 name:a16,age:49 name:a17,age:31 name:a18,age:34 name:a20,age:41 name:a21,age:46 name:a22,age:46 name:a23,age:43 name:a24,age:37 name:a25,age:31 name:a1,age:28 name:a2,age:48 name:a4,age:39 name:a6,age:41 name:a7,age:37 name:a8,age:33 name:a9,age:44 name:a10,age:25 name:a11,age:31 name:a12,age:19 name:a13,age:35 name:a14,age:31 name:a15,age:46 name:a16,age:49 name:a17,age:31 name:a18,age:36 name:a20,age:41 name:a21,age:48 name:a22,age:48 name:a23,age:43 name:a24,age:37 name:a25,age:31