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();
        }
原文地址:https://www.cnblogs.com/anjingdian/p/13069924.html