zoukankan      html  css  js  c++  java
  • 委托/lambda表达式/事件

    委托

    委托是执行安全的类,它的使用方式与类类似(即都需要定义再实例化),不同在于,类在实例化之后叫对象或类的实例,但委托在实例化后仍叫委托,委托可以把函数作为参数传递.

    语法声明:

    delegate <return type> <delegate-name> <parameter list>

    委托使用的步骤:

    1. 声明委托类型(delegate关键字)
    2. 使用该委托类型声明一个委托变量并增加方法
    3. 调用委托执行方法

    定义两个方法

    public void ChineseSayHi()
    {
        Console.WriteLine("你好");
    }
    
    public static void EnglishSayHi()
    {
        Console.WriteLine("Hello");
    }

    第一步:委托定义,根据返回值和参数定义不同的委托,意义:任何一个无返回值且无参数的方法都可以使用这个委托来调用

     public delegate void MyDelegate();

     第二步:实例化委托变量并增加方法

    MyDelegate myDelegate = new MyDelegate(new Program().ChineseSayHi); //C#1.0及更高版本中使用这种方式

    也可以是静态方法

    MyDelegate myDelegate = new MyDelegate(EnglishSayHi);

    C# 2.0 提供了更简单的方法来编写前面的声明

    MyDelegate myDelegate = EnglishSayHi

     在 C# 2.0 和更高版本中,还可以使用匿名方法来声明和初始化委托

    MyDelegate myDelegate = delegate () { Console.WriteLine("匿名方法初始化委托"); };

     在 C# 3.0 和更高版本中,还可以通过使用 lambda 表达式声明和实例化委托

    //TODO

    第三步:调用委托执行方法,因为myDelegate是委托类型的一个变量.所以C#编译器会用myDelegate.Invoke()代替myDelegate(),所以这两种方式都可以

    myDelegate();
    myDelegate.Invoke();

    委托数组

    定义Math类提供两个静态方法接收一个double类型的参数,用于计算倍数和阶乘

    class Math
        {
            public static double MultipleTwo(double value)
            {
                return value * 2;
            }
            public static double Square(double value)
            {
                return value * value;
            }
        }

     添加MathOperation操作方法,传递委托和double类型参数

    class Program
        {
            public delegate double MyDelegate(double value);
    
            static void Main(string[] args)
            {
                //定义委托数组
                MyDelegate[] myDelegates = {
                    Math.MultipleTwo,
                    Math.Square
                };
                //使用委托数组
                for (int i = 0; i < myDelegates.Length; i++)
                {
                    MathOperation(myDelegates[i], 3.7);
                    MathOperation(myDelegates[i], 3.0);
                }
                Console.ReadKey();
            }
            public static void MathOperation(MyDelegate myDelegate, double value)
            {
                var result = myDelegate(value);
                Console.WriteLine("Delegate is {0},value is {1}", myDelegate,result);
            }
        }

     上面都是通过自己定义委托类型来使用的,除了为每个参数和返回类型定义一个新委托类型之外,还可以使用Action<T>和Func<T>委托

    Action<T>:表示引用一个void返回类型的方法,可以传递最多16种不同的参数类型,没有泛型参数的Action类可调用没有参数的方法

    Func<T>:Func<T>允许调用带返回类型的方法,可以传递16种不同类型的参数和一个返回类型,Func<out TResult>委托类型可以调用带返回类且无参数的方法

    Action<T>实例

     Action action = EnglishSayHi;
     action.Invoke();

    Func<T>实例

    Func<double, double>[] func = {
                    Math.MultipleTwo,
                    Math.Square
                };

     总结

    1:Action用于没有返回值的方法(参数根据自己情况进行传递)

    2:Func恰恰相反用于有返回值的方法(参数根据自己情况传递)

    3:记住无返回就用Action,有返回就用Func

    多播委托
    之前的每个委托都只包含一个方法调用,调用委托的次数与调用方法的次数相同,如果要调用多个方法,就需要多次显式调用这个委托,但是委托中也可以包含多个方法,称为多播委托,多播委托可以按顺序调用多个方法,为此委托的签名必须返回void,否则就只能得到委托最后调用的最后一个方法的结果

    Func<double, double> func = Math.MultipleTwo;
    func += Math.Square;
    var result = func(3.0);
    Console.WriteLine(result);
    
    MyDelegate myDelegate = Math.MultipleTwo;
    myDelegate += Math.Square;
    var result2 = myDelegate(3.0);
    Console.WriteLine(result2);
    Console.ReadKey(); 

    只返回了3.0阶乘的值

    这里需要再回头仔细了解一下看这种说法到底是否正确:即多播委托返回值必须为void

    多播委托使用+=和-=,在委托中增加或删除方法调用

    class Program
    {
        public delegate double MyDelegate(out double value);
    
        static void Main(string[] args)
        {
            Action action = Print.First;
            action += Print.Second;
            action();
            Console.ReadKey();
        }     
    }
    class Print
    {
        public static void First()
        {
            Console.WriteLine("FirstMethod");
        }
        public static void Second()
        {
            Console.WriteLine("SecondMethod");
        }
    }

    如果要使用多播委托,就要知道对同一个委托调用方法链的顺序并未正式定义,因为要避免编写依赖于特定顺序调用方法的代码

    使用多播委托,意味着多播委托里包含一个逐个调用的委托集合,如果集合其中一个方法抛出异常.整个迭代就会停止

    class Program
        {
            public delegate double MyDelegate(out double value);
    
            static void Main(string[] args)
            {
                Action action = Print.First;
                action += Print.Second;
                action();
                Console.ReadKey();
            }
        }
        class Print
        {
            public static void First()
            {
                Console.WriteLine("FirstMethod");
                throw new Exception("Error");
            }
            public static void Second()
            {
                Console.WriteLine("SecondMethod");          
            }
        }

    委托只调用了第一个方法,因为第一个方法抛出了异常,委托的迭代停止,不再调用Second()方法

    使用Delegate的GetInvocationList()方法自己迭代方法列表

                Action action = Print.First;
                action += Print.Second;
    Delegate[] delegates
    = action.GetInvocationList(); foreach (Action item in delegates) { try { item(); } catch (Exception error) { Console.WriteLine(error.Message); } } Console.ReadKey();

     修改后,程序在捕获异常后,会迭代下一个方法

    匿名类型

    之前要使用委托,方法必须已经存在,还有另外一种使用委托的方式,即通过匿名方法,匿名方位作为委托参数的一部分;如果某个方法只被使用一次,这种情况下,除了创建委托语法的需要,没有必要创建独立的具名方法。因此,匿名方法应运而生

    static void Main(string[] args)
            {
                Action action = delegate ()
                {
                    Console.WriteLine("匿名方法打印Hello");
                };
                Action<string> action2 = delegate (string name)
                {
                    Console.WriteLine("你好,我叫{0}", name);
                };
                Func<double, double> func = delegate (double value)
                {
                    Console.WriteLine("Value={0}", value * 2);
                    return value * 2;
                };
                action();
                action2("Wang");
                func(3.0);
                Console.ReadKey();
            }

    lambda表达式

    C#3.0开始,可以使用一种新的语法把实现代码赋予委托,这就是lambda表达式,Lambda表达式取代了匿名方法,作为编写内联代码的首先方式

    lambda运算符"=>"左边列出了需要的参数,右边定义了赋予lambda变量的方法实现代码

    无参数

    Action action = () =>
    {
          Console.WriteLine("无参数");
    };

    一个参数

    Action<string> action2 = param =>
    {
           Console.WriteLine("你好,我叫{0}", param);
    };

     多个参数

    Action<int, int> action2 = (a, b) =>
    {
           Console.WriteLine("Sum:{0}", a + b);
    };

     如果lambda表达式只有一句.方法块内就可以省略花括号和return语句,这时编译器会添加一条隐式的return语句

     Func<double, double> func = param => param * param;

     等价于

    Func<double, double> func = param =>
    {
        return param * 2;
    };

     闭包

    通过lambda表达式可以访问表达式块外部的变量,称为闭包,编译器会创建匿名类,使用构造函数来传递外部变量,构造函数取决于从外部传递进来的变量个数

    static void Main(string[] args)
            {
                int x = 3;
                Action<int> action = p =>
                {
                    Console.WriteLine("lambda表达式调用外部变量,Sum :{0}", p + x);
                };
                Action<int> action2 = delegate (int p)
                {
                     Console.WriteLine("匿名函数调用外部变量,Sum {0}", p + x);
                };
                action(3);
                action2(3);
                Console.ReadKey();
            }

    这里需要后续着重学习一下,包括闭包的陷阱等等

    事件 

    事件基于委托,为委托提供了一种发布/订阅机制.

    事件定义语法

    <访问修饰符> event 委托名 事件名;

    看到一个孩子他妈叫他爸和儿子回家吃饭的例子,觉着很生动.这里就用这个例子学习一下

     class Program
        {
            static void Main(string[] args)
            {
                Mum mum = new Mum();
                //事件订阅
                mum.EatEvent += new Mum.myDelegate(new Son().Eat);
                mum.EatEvent += new Mum.myDelegate(new Father().Eat);
                mum.Call();
                Console.ReadKey();
            }
        }
        /// <summary>
        /// 发布器
        /// </summary>
        public class Mum
        {
            //定义委托,用这个委托定义处理事件的方法类型
            public delegate void myDelegate();
    
            //根据委托定义事件,这里的事件是吃饭
            public event myDelegate EatEvent;
    
            public void Call()
            {
                Console.WriteLine("Mum:饭做好了,过来吃饭");
                //触发事件
                EatEvent();
            }
        }
        class Son
        {
            //事件处理方法,与Mum类中的委托方法签名一致
            public void Eat()
            {
                Console.WriteLine("Son:游戏打完我就过去");
            }
        }
        class Father
        {
            public void Eat()
            {
                Console.WriteLine("Father:好的我马上过来");
            }
        }

     

     event与EventHandler

    待补充...

    参考:

    http://www.cnblogs.com/jujusharp/archive/2011/08/04/2127999.html

    https://www.cnblogs.com/HQFZ/p/4903400.html

    https://www.cnblogs.com/wangjiming/p/8300103.html

  • 相关阅读:
    (三)xpath爬取4K高清美女壁纸
    聚焦爬虫:数据解析
    (二)requests-爬取国家药监局生产许可证数据
    (一)requests-实战小练习
    requests模块
    spring+apache dbcp +oracle 连接池配置以及优化
    IntelliJ IDEA 注释模版 输入/**后 不显示配置好的模板
    oracle 隔离级别、事务怎么开始的以及如何查看数据库采用字符集
    java 日期处理相关
    Oracle 插入数据时获取系统时间
  • 原文地址:https://www.cnblogs.com/GnailGnepGnaw/p/10633810.html
Copyright © 2011-2022 走看看