• c#之委托


    引用:https://www.bilibili.com/video/BV1wx411K7rb?p=19

    1.委托就是c语言中的函数指针(指向函数的指针)的升级版,可以按照一定的约束,将对方法的引用作为实参传给另一个方法。

    2.程序员常说一切皆地址,程序的本质就是数据加算法,数据是存储在变量中的,本质是地址,函数代表了算法,本质也是地址。

    变量(数据)是以某个内存地址为起点的一段内存中存储的值。至于数据多大,取决于存储地址的类型决定的。

    函数(算法)是以某个内存地址为起点的一段内存中所存储的机器语言指令。

    CPU就是按照这些机器语言指令来执行,完成算法。

    变量是用来寻找数据的地址,函数是用来寻找算法的地址,这就是常说的一切皆地址。

    3.直接调用和间接调用

    (1)直接调用:通过函数名来调用函数。CPU通过函数名直接获得函数所在地址并执行函数。

    (2)间接调用:通过函数指针调用函数,函数指针作为一个变量,里面存储的就是这个函数的地址,CPU通过获取函数指针存储的值获得函数所在地址并开始执行。

    4.委托就是一个对象,它知道如何调用一个方法。

    (1)委托类型:定义了委托实例可以调用的那类方法,具体说就是委托类型定义了方法的返回类型和参数

    比如下面:

     这是一个委托类型,定义了这个委托可以调用返回类型为int,参数为int的方法,

      delegate int Transformer(int x);

    比如下面2个:

    static int Squre(int x) { return x * x; }
    
    static int Squre(int x) => x * x;

    委托实例:把方法赋给委托变量的时候就创建了委托实例,比如下面:

    Transformer t=Squre;

    调用:

    int value = t(3);

    下面是一个简单例子:解耦(降低耦合度)

     class Program
        {
            delegate int Transformer(int x);
            static int Squre(int x) { return x * x; }
             
            static void Main(string[] args)
            {
                //创建委托实例
                Transformer t = Squre;
                int result = t(3);//委托实例来调用目标方法,这样会实现解耦
                Console.WriteLine(result);
                Console.ReadKey();
                 
                
            }
        
        }

    Func<Result>,Func<T1,Result>是一个.Net内置的泛型委托。

    • Func<TResult>
    • Func<T,TResult>
    • Func<T1,T2,TResult>
    • Func<T1,T2,T3,TResult>
    • Func<T1,T2,T3,T4,TResult>

    它有5种形式,只是参数个数不同;第一个是无参数,但是有返回值

    Action<T>的用法与Func几乎一样,调用方法也类似,但是没有返回值。

    • Action
    • Action<T>
    • Action<T1,T2>
    • Action<T1,T2,T3>
    • Action<T1,T2,T3,T4>

    下面是一个简单的例子:

    namespace TestClass
    {
        public delegate double Calc(int a, int b);
        class Program
        {
            public delegate double Calc(int a,int b);
            static void Main(string[] args)
            {  
                //调用Report方法
                Calculator calculator = new Calculator();
                //calculator.Report:把Calculator类中的Report对象名称给了action,
                //CPU通过获取函数指针存储的值获得函数所在地址并开始执行。
                Action action = new Action(calculator.Report);
                //执行,下面2种方式都可以。
                action.Invoke();
                action();
                //Func
                Func<int, int, int> func1 = new Func<int, int, int>(calculator.Add);
                Func<int, int, int> func2 = new Func<int, int, int>(calculator.Sub);
                func1.Invoke(1,2);
                func1(1,2);
                func1.Invoke(5,6);
                func1(5,6);
                //自定义委托
                Calc cal = new Calc(calculator.Test);
                cal(7,8);
                Console.WriteLine(cal(7, 8));
                Console.ReadKey();
            }
        }
        public  class Calculator
        {
    
            public void Report()
            {
    
                Console.WriteLine("Report");
            }
            public int  Add(int a,int b)
            {
                return a + b;
            }
            public int Sub(int a, int b)
            {
                return a - b;
            }
            public double Test(int a, int b)
            {
                return a - b;
            } 
        } 
    }

    委托也是一种类,所以也是数据类型,比如下面:

                Type type = typeof(Action);
                Console.WriteLine(type.IsClass);

    结果:True

    5.委托的一般使用

      委托将方法作为参数传递给另一个方法又细分为2种:

    1.模板方法

    比如写了一个方法,然后通过传进来的委托参数,"借用"指定的外部方法来产生一个结果。常位于代码中部,委托有返回值。

    2.回调方法

    调用指定的外部方法,常位于代码的末尾,委托无返回值。比如一个人给了你一张名片,有事情的时候可以找他帮忙,没有事情的话就不用找他帮忙。此时就构成了回调关系,就是可以调用,也可以不调用。而且回调方法可以让我们动态的调用选择回调的方法,就比如你从一堆名片中选出一个寻求帮助。回调方法一般都是执行后续的工作。

    还有一个例子,就是一个人在面试,面试官告诉他如果通过了就会打电话通知,此人不需要打电话问。

    下面是模板方法的实例:

    namespace TestClass
    { 
        class Program
        {
          
            static void Main(string[] args)
            {
                ProductFactory productFactory = new ProductFactory();
                WrapFactory wrapFactory = new WrapFactory();
                Func<Product> func1 = new Func<Product>(productFactory.MakePizza);
                Func<Product> func2 = new Func<Product>(productFactory.MakeToCar);
                Box box1 = wrapFactory.WrapProduct(func1);
                Box box2 = wrapFactory.WrapProduct(func2);
    
                Console.WriteLine(box1.Product.Name); 
                Console.WriteLine(box2.Product.Name);
                Console.ReadKey();
            }
        }
        /// <summary>
        /// 产品类
        /// </summary>
        public  class Product
        {
             public string Name { get; set; } 
        }
        /// <summary>
        /// 包装箱
        /// </summary>
        public class Box
        {
            /// <summary>
            /// 里面含有的产品
            /// </summary>
            public Product Product { get; set; }
        }
        /// <summary>
        /// 专门包装产品的工厂
        /// </summary>
        public  class  WrapFactory
        {
            /// <summary>
            /// 这是一个模板方法,接收产品,进行包装,并返回包装完的产品。
            /// 这里接收的是Product类参数,
            /// </summary>
            /// <param name="getProduct"></param>
            /// <returns></returns>
            public Box WrapProduct(Func<Product> getProduct)
            {
                Box box = new Box();
                //调用对应的产品方法, 也可以写成getProduct.Invoke()
                Product product = getProduct();
                box.Product = product;
                return box;
            }
        }
        /// <summary>
        /// 专门生产产品的工厂
        /// </summary>
        public class ProductFactory
        {
            /// <summary>
            /// 这是一个模板方法,接收产品,进行包装,并返回包装完的产品。
            /// 这里接收的是Product类参数,
            /// </summary>
            /// <param name="getProduct"></param>
            /// <returns></returns>
            public Product MakePizza()
            {
               
                Product product =new  Product();
                product.Name = "Pizza";
                return product;
            }
            public Product MakeToCar()
            { 
                Product product = new Product();
                product.Name = "Car";
                return product;
            }
        }
    
    
    
    
    }

    结果:

    Pizza
    Car

    使用模板方法的好处:从上面代码我们可以看出,此时Box类,WrapFactory类,都不用再动,只需要不停的扩展产品工厂。不管我们生产什么产品,只要封装在一个委托对象中,传给模板方法,就可以包装成一个箱子返回。

    回调方法示例:

    namespace TestClass
    { 
        class Program
        {
          
            static void Main(string[] args)
            {
                ProductFactory productFactory = new ProductFactory();
                WrapFactory wrapFactory = new WrapFactory();
                Func<Product> func1 = new Func<Product>(productFactory.MakePizza);
                Func<Product> func2 = new Func<Product>(productFactory.MakeToCar);
                Logger logger = new Logger();
                Action<Product> action = new Action<Product>(logger.Log);
                Box box1 = wrapFactory.WrapProduct(func1, action);
                Box box2 = wrapFactory.WrapProduct(func2, action);
    
                Console.WriteLine(box1.Product.Name); 
                Console.WriteLine(box2.Product.Name);
                Console.ReadKey();
            }
        }
        /// <summary>
        /// 产品类
        /// </summary>
        public  class Product
        {
             public string Name { get; set; } 
            public double Price { get; set; }
        }
        /// <summary>
        /// 日志
        /// </summary>
        public class  Logger
        {
            public  void Log(Product product )
            {
                Console.WriteLine("时间"+DateTime.UtcNow);
            }
        }
        /// <summary>
        /// 包装箱
        /// </summary>
        public class Box
        {
            /// <summary>
            /// 里面含有的产品
            /// </summary>
            public Product Product { get; set; }
        }
        /// <summary>
        /// 专门包装产品的工厂
        /// </summary>
        public  class  WrapFactory
        {
            /// <summary>
            /// 这是一个模板方法,接收产品,进行包装,并返回包装完的产品。
            /// 这里接收的是Product类参数,
            /// </summary>
            /// <param name="getProduct"></param>
            /// <returns></returns>
            public Box WrapProduct(Func<Product> getProduct,Action<Product> action)
            {
                Box box = new Box();
                //调用对应的产品方法, 也可以写成getProduct.Invoke()
                Product product = getProduct(); 
                if(product.Price>=50)
                {
                    //当做回调函数用。
                    action(product);
                }
                box.Product = product;
                return box;
            }
        }
        /// <summary>
        /// 专门生产产品的工厂
        /// </summary>
        public class ProductFactory
        {
            /// <summary>
            /// 这是一个模板方法,接收产品,进行包装,并返回包装完的产品。
            /// 这里接收的是Product类参数,
            /// </summary>
            /// <param name="getProduct"></param>
            /// <returns></returns>
            public Product MakePizza()
            {
               
                Product product =new  Product();
                product.Name = "Pizza";
                product.Price = 12;
                return product;
            }
            public Product MakeToCar()
            { 
                Product product = new Product();
                product.Name = "Car";
                product.Price = 102;
                return product;
            }
        }
    
    
    
    
    }

    结果:

    时间2020/6/21 5:39:00
    Pizza
    Car

    注意,委托使用不当会造成下面的问题:

    1.这是一种方法级别的紧耦合,现实工作中要慎用。

    2.可读性下降,debug难度增加。

    3.把委托调用,异步调用,多线程纠缠在一起,会让代码变得难以阅读和维护。

    4.使用不当会造成性能下降和内存泄漏。因为委托会引用一个方法,这个方法是一个实例方法的话,那这个方法是一定属于一个对象,拿委托引用这个方法,这个对象就必须存在内存当中,就算是没有其他的引用类性引用这个对象,这个对象的内存也不能释放,因为一旦释放,委托就不能间接调用对象的方法了。所以可能会造成内存泄漏,并引起性能下降。

    6.多播委托

    包含多个方法的委托成为多播委托,调用多播委托,可以按照顺序连续调用多个方法,因此,委托的签名就必须返回void;

    例子如下:

    namespace TestClass
    { 
        class Program
        {
          
            static void Main(string[] args)
            {
                Student student1 = new Student() {ID=1,PenColor=ConsoleColor.Black };
                Student student2 = new Student() { ID = 2, PenColor = ConsoleColor.Green };
                Student student3 = new Student() { ID = 3, PenColor = ConsoleColor.Yellow };
                Action action = new Action(student1.DoWork);
                Action action2 = new Action(student1.DoWork);
                Action action3 = new Action(student1.DoWork);
                //将action2,action3赋给了action,这样就可以只执行action的方法就可以把所有委托实现
                action += action2;
                action += action3;
                action.Invoke();
                
                Console.ReadKey();
            }
        }
        public class Student
        {
            public int ID { get; set; }
            public ConsoleColor PenColor { get; set; }
    
            public void DoWork()
            {
                for(int i=0;i<5;i++)
                {
                    Console.ForegroundColor = this.PenColor;
                    Console.WriteLine($"Student {ID} doing homework {i} hours");
                    Thread.Sleep(1000);
                }
            }
    
    
        }
    
    
    
    
    
    
    }

    7.隐式异步调用

    同步指的是从上向下顺序调用。

    隐式的异步调用BeginInvoke方法即可;BeginInvoke(null,null);第一个参数是回调函数,后面是参数值。

    8.显式异步调用

     static void Main(string[] args)
            {
                Student student1 = new Student() { ID = 1, PenColor = ConsoleColor.Black };
                Student student2 = new Student() { ID = 2, PenColor = ConsoleColor.Green };
                Student student3 = new Student() { ID = 3, PenColor = ConsoleColor.Yellow }; 
                Task task1 = new Task(new Action(student1.DoWork));
                Task task2 = new Task(new Action(student2.DoWork));
                Task task3 = new Task(new Action(student3.DoWork));
                task1.Start();
                task2.Start();
                task3.Start();
                for (int i = 0; i < 5; i++)
                {
                    Console.WriteLine($"第{i}次");
                    Thread.Sleep(1000);
                }
                Console.ReadKey();
            }
  • 相关阅读:
    mysql 中 时间函数 now() current_timestamp() 和 sysdate() 比较
    在spring boot 中使用itext和itextrender生成pdf文件
    dockerfile构建的镜像
    在linux环境下使用itext生成pdf
    在spring data jpa中使用自定义转换器之使用枚举转换
    Sping Boot返回Json格式自定义
    【强化学习RL】model-free的prediction和control — MC, TD(λ), SARSA, Q-learning等
    【强化学习RL】必须知道的基础概念和MDP
    【GAN与NLP】GAN的原理 —— 与VAE对比及JS散度出发
    【NLP】使用bert
  • 原文地址:https://www.cnblogs.com/anjingdian/p/13069924.html
Copyright © 2020-2023  润新知