• 委托的简介、使用和简单事件


    委托
    编程中,可能会遇到以下逻辑
    需求:
    写一个方法,输出数组中满足条件(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
  • 相关阅读:
    NAND FLASH扇区管理
    ECC内存校验算法
    实时数据库简介
    windows标准控件
    PLC一些资料
    at命令
    Vi 常用命令列表
    PHP继承及实现
    Mongodb php扩展及安装
    Linux下jdk1.6安装指引
  • 原文地址:https://www.cnblogs.com/wanjinliu/p/11658778.html
Copyright © 2020-2023  润新知