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);
//也可以按照下面形式调用

   Box box1 = wrapFactory.WrapProduct(productFactory.MakePizza);

                Box box2 = wrapFactory.WrapProduct(productFactory.MakeToCar);

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

 内存泄漏:是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄漏似乎不会有大的影响,但内存泄漏堆积后的后果就是内存溢出。 

 内存溢出:指程序申请内存时,没有足够的内存供申请者使用,或者说,给了你一块存储int类型数据的存储空间,但是你却存储long类型的数据,那么结果就是内存不够用,此时就会报错OOM,即所谓的内存溢出。 

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);
            }
        }


    }






}

 还可以实现下面的调用,可以传参数:

 class Program
    { 
        static void Main(string[] args)
        {
            Student student = new Student();
            student.Show();
            Console.ReadKey();
        }

      
    }
    public class Student
    {
        public Action action;
        public void Show()
        {
            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 += new Student().DoWork;
            //可以传参数
            action += () => new Student().DoWork1("s");
            action.Invoke();

        }
        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");

            }
        }
 
        public void DoWork1(string name)
        {
            for (int i = 0; i < 5; i++)
            {
                Console.ForegroundColor = this.PenColor;
                Console.WriteLine($"Student {ID} doing homework {i} hours");

            }
        }


    }

 

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();
        }

最后,为什么要使用委托的一个例子:https://www.cnblogs.com/xiaoyehack/p/9647526.html 

 9.委托的扩展:俄罗斯套娃(类似于asp.netcore中的管道模型原理)

如上图,方法一层一层的递进,最后一层一层的出来。

  public   class DelegateExtend
    {
        /// <summary>
        /// 俄罗斯套娃内部原理
        /// </summary>
        public static void Show()
        {
            //定义一个委托,参数和返回值都是委托
            Func<Action, Action> func1 = new Func<Action, Action>(act =>
            {
                return new Action(() =>
                {
                    Console.WriteLine("func1 start");
                     act?.Invoke();
                    Console.WriteLine("func1 end");

                });

            });
            Func<Action, Action> func2 = new Func<Action, Action>(act =>
            {
                return new Action(() =>
                {
                    Console.WriteLine("func2 start");
                     act?.Invoke();
                    Console.WriteLine("func2 end");

                });

            });
            Func<Action, Action> func3 = new Func<Action, Action>(act =>
            {
                return new Action(() =>
                {
                    Console.WriteLine("func3 start");
                    act?.Invoke();
                    Console.WriteLine("func3 end");

                });

            });

            List<Func<Action, Action>> list = new List<Func<Action, Action>>() { func1, func2, func3 };
            list.Reverse();//反转
            Action action = null;
            foreach (var act in list)
            {
                action = act.Invoke(action);
             
            } 
            action.Invoke();

        }


    }

结果:

func1 start
func2 start
func3 start
func3 end
func2 end
func1 end

 

posted @ 2020-06-09 00:33  安静点--  阅读(291)  评论(0编辑  收藏  举报