万金流
以码会友。 吾Q:578751655。 水平有限,轻喷,谢!

委托
编程中,可能会遇到以下逻辑
需求:
写一个方法,输出数组中满足条件(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

 

posted on 2019-10-14 08:14  万金流  阅读(542)  评论(0编辑  收藏  举报