• C#基础篇——委托


    前言

    在本章中,主要是借机这个C#基础篇的系列整理过去的学习笔记、归纳总结并更加理解透彻。

    在.Net开发中,我们经常会遇到并使用过委托,如果能灵活的掌握并加以使用会使你在编程中游刃有余,然后对于很多接触C#时间不长的开发者而言,较好的理解委托和事件并不容易。

    本节主要是讲述对委托的定义、委托的使用、多播委托、泛型委托、匿名方法、Func和Action委托、Lambda委托,并对它们进行讨论。

    说明

    简单说它就是一个能把方法当参数传递的对象,而且还知道怎么调用这个方法,同时也是粒度最小的“接口”(约束了指向方法的签名)。

    开始

    1.定义委托

    委托:是一种定义方法签名的类型。 当实例化委托时,可以将其实例与任何具有兼容签名的方法相关联。 可以通过委托实例调用方法。

    这里引用一个网友的说法:

    某人有三子,让他们各自带一样东西出门,并带回一头猎物。
    上面一句话可以理解为父亲对儿子的委托:猎物 办法(工具 某工具)-->delegate 猎物(返回值) 带回猎物(委托名)(工具(参数类型) x)-->delegate int GetValue(int i)
    三个人执行委托的方法各不相同
    兔子 打猎(工具 弓)-public static int GetValue1(int i){ return i; }
    野鸡 买(工具 钱)-public static int GetValue2(int i){ return i*2; }
    狼 诱捕(工具 陷阱)-public static int GetValue3(int i){ return i*i; }
    

    2.简单的使用

    一个委托类型定义了该类型的实例化时能调用的一类方法,这些方法含有同样的返回类型和同样参数(类型和参数个数相同)

    比如:定义一个委托

    delegate int Calculator (int x);
    

    此委托适用于有着int返回类型和一个int类型参数方法。

    static int Double (int x) { return x * 2; }
    

    创建一个委托实例,并将方法赋值给委托实例

    Calculator c = new Calculator(Double);
    //或者另一种写法
    Calculator c = Double;
    

    通过委托实例的调用

    int result = c(2);
    

    3.多播委托

    在开发中,我们有时候会遇到要通过调用一个委托,同时可以执行多个方法的时候,就可以考虑用多播委托。调用多个委托需要多次显示调用这个委托。所有的委托实例都可以包含多个方法,实现多播功能。

    这个打个比方:多播,就像一群程序员在瞬聘网填好了求职意向后,某天有个公司发布了一个和这些程序员求职意向刚好相匹配的工作,然后这些求职者都被通知了 - “有一份好工作招人啦,你们可以直接申请去上班了!”。
    也就是说,一个委托实例不仅可以指向一个方法,还可以指向多个方法。
    多播委托,提供了一种类似于流水线的钩子机制,只要加载到这条流水线上的委托,都会被顺序执行。因为所有的都继承自MulticastDelegate,因此所有的委托都具有多播特性
    
            //声明一个委托,委托返回值为void
            public delegate void Greetings(String name);
    
            public static void Hello(String name)
            {
                Console.WriteLine("您好, {0}!", name);
            }
    
            public static void GoodBye(String name)
            {
                Console.WriteLine("再见, {0}!", name);
            }
    
            public static void Main()
            {
                Greetings greetings = Hello;
                //使用+=给委托添加方法
                greetings += GoodBye;
                String name = "艾三元";
                Console.WriteLine("这是一种调用方法:");
                //第一种执行方式
                greetings(name);
                //第二种执行方式
                Console.WriteLine("这是另一种使用方法");
                //返回委托的调用列表。
                Delegate[] delegates = greetings.GetInvocationList();
                //注意这里的delegates列表中存储的是Greetings类型的委托
                foreach (Greetings greeting in delegates)
                {
                    greeting(name);
                }
                Console.ReadKey();
            }
    

    1748233452

    说明:

    • 如果是多播委托,委托的签名就必须返回 void ,否则,返回值应送到何处?当委托只包含一个方法的时候,则可以通过所封装的方法发现其返回类型的声明,不一定必须是void。实际上,如果编译器发现某个委托返回 void ,就会自动假定这是一个多播委托。

    • “+=” 用来添加,“-=”用来从委托中删除方法调用

    4.泛型委托

    在之前的篇章中,我们已经学会了什么是泛型,因此,也方便我们理解泛型委托,简单的说,就是一种含有泛型参数的委托。

    		public delegate T Calculator<T>(T arg);
            static int Double(int x) { return x * 2; }
    
            static class Utility
            {
                public static void Calculate<T>(T[] values, Calculator<T> c)
                {
                    for (int i = 0; i < values.Length; i++)
                        values[i] = c(values[i]);
                }
            }
            static void Main(string[] args)
            {
                int[] values = { 11, 22, 33, 44 };
    
                Utility.Calculate(values, Double);
                foreach (int i in values)
                    Console.Write(i + " "); // 22 44 66 88
                Console.ReadKey();
            }
    

    5. 匿名方法

    匿名方法,是在初始化委托时候内联声明的方法。

    每次实例化一个委托时,都需要事先定义一个委托所要调用的方法。为了简化这个流程,C# 2.0开始提供匿名方法来实例化委托。这样,我们在实例化委托时就可以 “随用随写” 它的实例方法。

        static string GetNumber(string str)
        {
            return str;
        }
        delegate string DelNumber(string str);
        static void Main(string[] args)
        {
            //声明一个名称为GetNumber的具名方法
            DelNumber delNumber1 = GetNumber;
            Console.WriteLine(delNumber1("这是具名方法"));
    
            //匿名方法 ,未在别的地方定义方法,而是直接把方法写在实例化代码中
            DelNumber delNumber2 = delegate (string str)
            {
                return str;
            };
            Console.WriteLine(delNumber2("这是匿名方法调用"));
            Console.ReadKey();
        }
    
        #endregion
    

    通过以上简单的示例看出:

    匿名方法的语法:关键字delegate {参数列表}{语句块}

    delegte { Paramters} {ImplementationCode}

    		delegate (string str)
            {
                return str;
            };
    

    使用的格式是:

    委托类名 委托实例名 = delegate (args) {方法体代码} ;

    		 delegate string DelNumber(string str); //委托类型的返回类型
            //匿名方法 ,未在别的地方定义方法,而是直接把方法写在实例化代码中
            DelNumber delNumber2 = delegate (string str)
            {
                return str;                         //根据返回类型,返回一个string类型
            };
    

    这样就可以直接把方法写在实例化代码中,不必在另一个地方定义方法。当然,匿名委托不适合需要采用多个方法的委托的定义。需要说明的是,匿名方法并不是真的“没有名字”的,而是编译器为我们自动取一个名字。

    可以在以下地方使用匿名方法:

    • 声明委托变量时为初始化表达式。
    • 组合委托时在赋值语句的右边。
    • 为委托增加事件时在赋值语句的右边。

    6.Func 和 Action 委托

    在之前,我们在使用委托的时候,都是自定义一个委托类型,再使用这个自定定义的委托定义一个委托字段或变量。而在后续的编程语言中又新加入了一种特性,C#语言预先为我们定义了两个常用的委托,一个是Func,一个是Action,还带来了Lambda,这使得委托的定义和使用变得简单起来, 在以后进行C#程序编写中引入委托更加灵活。

    Action委托

    C#中与预定义了一个委托类型Action,基本特点就是可以执行一个没有返回值,没有参数的方法。是一类没有输出参数的委托,但是输入参数可以为C#中的任意类型,即可以进行委托执行形式的方法。

        static void printString()
        {
            Console.WriteLine("Hello World");
        }
        static void printNumber(int x)
        {
            Console.WriteLine(x);
        }
        static void Main(String[] args)
        {
            //Action基本使用
            Action a = printString;
            a(); // 输出结果  Hello World
            //Action指向有参数的方法
            Action<int> b = printNumber; // 定义一个指向 形参为int的函C#数
            b(5); // 输出结果  5
        }
    

    Action可以通过泛型来指定,指向的方法有 0 - 16个参数

    Action<int, int, string, bool 等等>

    Func委托

    Func同样也是预定的委托,是一种由返回值的委托,传递0-16个参数,其中输入参数和返回值都用泛型表示。

      		static int GetNumber()
            {
                return 1;
            }
            static int GetNumber(string str)
            {
                return 1;
            }
            static void Main(string[] args)
            {
                Func<int> a = GetNumber; // 定义一个Func 委托,  指向一个返回int类型的 方法
                Console.WriteLine(a());
                Func<string, int> b = GetNumber; // 泛型中最后一个参数表示返回值类型。
                Console.WriteLine(b("Hello"));
            }	
    

    注意:Func<string, int> 最后一个参数表示返回值类型,前面的都是形参类型。

    7. Lambda表达式

    江山代有才人出,纵然匿名方法使用很方便,可惜她很快就成了过气网红,没能领多长时间的风骚。如今已经很少见到了,因为delegate关键字限制了她用途的扩展。自从C# 3.0开始,她就被Lambda表达式取代,而且Lambda表达式用起来更简单。Lambda表达式本质上是改进的匿名方法。

    在匿名方法中,delegate关键字有点多余,因为编译器已知将我们的方法赋值给委托。因此,我们很容易的将匿名方法的步骤转换为Lambda表达式:1. 删除delegate关键字。2.在参数列表和匿名方法主体之间放lambda运算符=>。

    DelNumber delNumber2 = delegate (string str){ return str;}; //匿名方法
    
    DelNumber delNumber2 =  (string str) =>{ return str;}; //Lambda方法
    

    Lambda表达式的灵感来源于数学中的Lambda积分函数表达式,例如下图:

    1270751451

    Lambda表达式把其中的箭头用 => 符号表示。

    上面的对比例子中,Lambda还可以进一步简化

    delegate string DelNumber(string str); //委托类型的返回类型
    DelNumber delNumber2 =  (string str) =>{ return str;}; //Lambda方法
    DelNumber delNumber3 =         (str) =>{ return str;}; //省略类型参数
    DelNumber delNumber4 =           str =>{ return str;}; //省略类型参数( 如果只有一个隐式类型参数,可以省略周围的圆括号)
    DelNumber delNumber5 =           str =>  str; //语句块替换为return关键字后的表达式 ( 如果只有一个返回语句,可以将语句块替换为return关键字后的表达式)
    

    如今Lambda表达式已经应用在很多地方了,例如方法体表达式(Expression-Bodied Methods)、自动只读属性表达式等等。

    Lambda表达式形式上分为两种:

    1.表达式Lambda
    当匿名函数只有一行代码时,可采用这种形式。例如:

    DelNumber delNumber= (s4, s5) => s4.Age <= s5.Age;
    

    其中=>符号代表Lambda表达式,它的左侧是参数,右侧是要返回或执行的语句。参数要放在圆括号中,若只有一个参数,为了方便起见可省略圆括号。有多个参数或者没有参数时,不可省略圆括号。

    相比匿名函数,在表达式Lambda中,方法体的花括号{}和return关键字被省略掉了。

    用的也是表达式Lambda,这是Lambda表达式的推广, 是C# 6 编译器提供的一个语法糖。

    2.语句Lambda
    当匿名函数有多行代码时,只能采用语句Lambda。例如,上面的表达式Lambda可改写为语句Lambda:

    DelNumber delNumber= (s4, s5) => 
    {
        //此处省略其他代码
        return s4.Age <= s5.Age;
    };
    

    语句Lambda不可以省略{}和return语句。

    完整示例

            delegate string DelNumber(string str); //委托类型的返回类型
            static void Main(string[] args)
            {
    
                DelNumber delNumber2 = (string str) => { return str; }; //Lambda方法
                DelNumber delNumber3 = (str) => { return str; }; //省略类型参数
                DelNumber delNumber4 = str => { return str; }; //省略类型参数( 如果只有一个隐式类型参数,可以省略周围的圆括号)
                DelNumber delNumber5 = str => str; //语句块替换为return关键字后的表达式 ( 如果只有一个返回语句,可以将语句块替换为return关键字后的表达式)
    
                Console.WriteLine(delNumber2("lambda"));
    
                Console.WriteLine(delNumber3("lambda"));
                Console.WriteLine(delNumber4("lambda"));
                Console.WriteLine(delNumber5("lambda"));
                Console.ReadKey();
            }
    

    注意:一个参数可以省略圆括号,多个参数必须圆括号,但是没有参数,必须使用一组空的圆括号

    如: (参数,参数)=>{语句} 或者 表达式
           (参数)  =>{语句} 或者 表达式
            参数   =>{语句} 或者 表达式
            ()    =>{语句} 或者 表达式
    

    总结

    1. 委托相当于用方法作为另一方法参数,同时,也可以实现在两个不能直接调用的方法中做桥梁,如在多线程中的跨线程的方法调用就得用委托。
    2. 熟悉在什么情况使用委托,在使用事件设计模式时,当需要封装静态方法时,当需要方便的组合时等多种情况下,可以加以使用。
    3. 如果有不对的或不理解的地方,希望大家可以多多指正,提出问题,一起讨论,不断学习,共同进步。
    4. 在下一节中,将对事件进行简单介绍,并总结归纳。

    参考 文档 《C#图解教程》

    注:搜索关注公众号【DotNet技术谷】--回复【C#图解】,可获取 C#图解教程文件

  • 相关阅读:
    判断操作系统
    Oracle之初体验
    判断PDF文件是否相同(通过二进制流判断)
    jQuery基础 html+css3+jquery 点击按钮切换内容
    Jquery基础,点击事件click,鼠标移入事件mouseover。通过事件改变DOM结构
    Jquery教学基础,简单的淡入淡出,隐藏显示,改变CSS等
    Vuex的高级使用及localStorage
    Vuex实现数据共享
    Vue项目代码结构简单介绍
    Vue项目环境准备
  • 原文地址:https://www.cnblogs.com/i3yuan/p/13052782.html
Copyright © 2020-2023  润新知