委托(C#入门详解学习笔记)

几个概念

一切皆地址

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

直接调用与间接调用

  • 直接调用:通过函数名来调用函数,CPU通过函数名直接获得函数所在地址并开始执行→返回
  • 间接调用:通过函数指针来调用函数,CPU通过读取函数指针存储的值获得函数所在地址并开始执行→返回

什么是委托

委托(delegate)可以理解为是C/C++中函数指针的”升级版“。

函数指针示例:

#include <stdio.h>

//函数指针
typedef int (*Cal)(int a, int b);

int Add(int a, int b)
{
	int result = a + b;
	return result;
}

int Sub(int a, int b)
{
	int result = a - b;
	return result;
}

int main()
{
	int x = 100;
	int y = 50;
	int z = 0;
	
    //将函数Add的地址给到函数指针function01
	Cal function01 = &Add;
    //将函数Sub的地址给到函数指针function02
	Cal function02 = &Sub;
	
    //直接调用
    z = Add(x,y);
	printf("%d+%d=%d\n",x,y,z);
    //间接调用
	z = function01(x,y);
	printf("%d+%d=%d\n",x,y,z);
	
    //直接调用
    z = Sub(x,y);
	printf("%d-%d=%d",x,y,z);
    //间接调用
	z = function02(x,y);
	printf("%d-%d=%d",x,y,z);
   
   return 0;
}

委托的声明(自定义委托)

委托是一种类(class),类是数据类型,所以委托也是一种数据类型。类可以声明变量、创建实例,所以委托也可以。

委托的声明格式与C#中一般的类的声明格式不同,反而更像是C/C++中函数指针的声明格式。这样做主要是为了照顾可读性,并与C/C++传统保持一致。

注意点:

  • 委托与所封装的方法必须”类型兼容“
    • 委托声明与所封装的方法的返回值的数据类型一致
    • 委托声明与所封装的方法的参数列表在个数和数据类型上一致
  • 注意声明委托的位置
    • 委托是一种类,声明时应该放在namespace中,使与其它类(class)于同一级别,避免放错位置结果声明成了嵌套类。

委托声明示例:

using System;

namespace CSharp_DelegateLearningExample
{
    public delegate double Calc(double x, double y); 

    class Program
    {
        static void Main(string[] args)
        {
            Calculator calculator = new Calculator();
            Calc calcAdd = new Calc(calculator.Add);
            Calc calcSub = new Calc(calculator.Sub);
            Calc calcMul = new Calc(calculator.Mul);
            Calc calcDiv = new Calc(calculator.Div);

            double x = 100;
            double y = 50;
            double z = 0;

            z = calcAdd.Invoke(x, y);
            Console.WriteLine(z);
            z = calcSub.Invoke(x, y);
            Console.WriteLine(z);
            z = calcMul(x, y);
            Console.WriteLine(z);
            z = calcDiv(x, y);
            Console.WriteLine(z);
        }
    }

    class Calculator
    {
        public double Add(double x, double y)
        {
            return x + y;
        }

        public double Sub(double x, double y)
        {
            return x - y;
        }

        public double Mul(double x, double y)
        {
            return x * y;
        }

        public double Div(double x, double y)
        {
            return x /y;
        }
    }
}

委托的常规使用

在日常工作当中,一般是使用委托封装方法,然后将委托作为参数,将委托封装的方法作为参数传递到另一个方法中使用。这样在另一个方法的方法体中就可以使用传进来的参数,间接地调用委托封装的那个方法,这样就形成了一种动态调用方法的代码结构。

而在具体使用过程中,可以分为两种方式:

  1. 模板方式:当前所写的方法,借用传递进来的委托参数所指定的外部方法来产生结果。

    • 委托有返回值
    • 常位于代码中部
    • 代码逻辑相当于”填空题“:空白处用传进来的委托类型的参数进行填补,也就是用传递进来的委托参数间接地调用所指定的外部方法。
  2. 回调方式:把委托类型的参数传进主调方法里面去,被传进主调方法里的委托类型的参数,内部封装了一个被回调的方法,主调方法根据自己的逻辑决定是否调用该回调方法。回调方法一般用来执行一些后续工作。

    • 委托无返回值

    • 代码逻辑相当于”流水线“

    • 常位于代码末尾

**注意:**委托功能强大,易使用,但难精通,一旦滥用则后果非常严重。

  1. 委托时方法级别的耦合,这种耦合往往违反设计模式,工作中要慎之又慎使用。
  2. 可读性下降,debug难度增加
  3. 委托回调、异步调用、多线程等纠缠在一起,则会非常难以维护
  4. 使用不当可能造成内存泄漏和程序性能下降
    • 委托所引用的一个方法,如果这个方法是一个实例方法的话,那么这个方法必定隶属于一个对象。该对象必定存在于内存当中,且不能被释放。因为一旦释放,委托则无法间接调用该方法了。所以可能产生内存泄漏。

示例:

using System;

namespace CSharp_DelegateLearning
{
    class Program
    {
        static void Main(string[] args)
        {
            ProductFactory productFactory = new ProductFactory();
            WrapFactory wrapFactory = new WrapFactory();

            Func<Product> funcMakePizza = new Func<Product>(productFactory.MakePizza);
            Func<Product> funcMakeToyCar = new Func<Product>(productFactory.MakeToyCar);

            Logger logger = new Logger();
            Action<Product> actionLog = new Action<Product>(logger.Log);

            Box box1 = wrapFactory.WrapProduct(funcMakePizza, actionLog);
            Box box2 = wrapFactory.WrapProduct(funcMakeToyCar, actionLog);

            Console.WriteLine(box1.Product.Name);
            Console.WriteLine(box2.Product.Name);
        }
    }

    class Logger
    {
        public void Log(Product product)
        {
            Console.WriteLine("Product {0} created at {1}. Price is {2}",
                product.Name, DateTime.UtcNow, product.Price);
        }
    }

    class Product
    {
        public string Name { get; set; }
        public double Price { get; set; }
    }

    class Box
    {
        public Product Product { get; set; }
    }

    class WrapFactory
    {
        /**此处getProduct为模板方式,logCallback为回调方式
         * 
         * 模板方式
         * 好处:该模板方式可以多次复用。
         * 使用此方式后,当需要增加产品时,
         * Product、Box、WrapFactory类均可以不改变,在ProductFactory中增加新产品的方法即可
         * 之后只需要将新产品的生产方法封装到一个委托中,就可以通过模板方法完成产品包装,
         * 从而最大限度实现代码重复使用
         * 
         * 回调方式
         * 好处:主调方法根据自己的逻辑决定是否调用该回调方法
         */
        public Box WrapProduct(Func<Product> getProduct, Action<Product> logCallback)
        {
            Box box = new Box();
            //传进来的委托封装的什么方法,这里委托调用就产生什么产品
            Product product = getProduct.Invoke();

            if (product.Price >= 50)
            {
                logCallback(product);
            }

            box.Product = product;
            return box;
        }
    }

    class ProductFactory
    {
        public Product MakePizza()
        {
            Product pizza = new Product();
            pizza.Name = "Pizza";
            pizza.Price = 12;
            return pizza;
        }

        public Product MakeToyCar()
        {
            Product toyCar = new Product();
            toyCar.Name = "Toy Car";
            toyCar.Price = 100;
            return toyCar;
        }
    }
}

通用泛型委托类型的简单使用(Func和Action)

在System命名空间下定义了Func和Action两个委托,它们使用了泛型类型参数。

image-20230222154445134

Func设置必须要有一个返回值参数,所以常用于对有返回值的函数设置委托;而Action委托则没有返回值参数。

Func和Action委托均可以使用0到16个输入参数

Func和Action委托使用示例:

using System;

namespace CSharp_DelegateLearning_FuncAction
{
    class Program
    {
        static void Main(string[] args)
        {
            Calculator calculator = new Calculator();
            Action action = new Action(calculator.Report);
            calculator.Report();  //直接调用
            action.Invoke();   //间接调用
            action();    //action.Invoke()的简便写法

            Func<int, int, int> funcAdd = new Func<int, int, int>(calculator.Add);
            Func<int, int, int> funcSub = new Func<int, int, int>(calculator.Sub);

            int x = 100;
            int y = 40;
            int z = 0;

            z = funcAdd.Invoke(x, y);
            z = funcAdd(x, y);
            Console.WriteLine(z);
            z = funcSub.Invoke(x, y);
            z = funcSub(x, y);
            Console.WriteLine(z);
        }
    }

    class Calculator
    {
        public void Report()
        {
            Console.WriteLine("I have three functions.");
        }

        public int Add(int x, int y)
        {
            return x + y;
        }

        public int Sub(int x, int y)
        {
            return x - y;
        }
    }
}

委托的高级使用

多播委托

多播委托,即一个委托内封装的不止一个方法。委托可以使用 + 和 += 运算符联结多个委托实例;可以用 - 和 -= 运算符从左侧委托操作数中将右侧委托操作数删除。

多播委托中方法的执行顺序,是封装方法的顺序。示例:

using System;
using System.Threading;

namespace CSharp_DelegateLearning_MulticastDelegate
{
    class Program
    {
        static void Main(string[] args)
        {
            Student stu1 = new Student() { Name = "zhao", PenColor = ConsoleColor.Green };
            Student stu2 = new Student() { Name = "qian", PenColor = ConsoleColor.Yellow };
            Student stu3 = new Student() { Name = "sun", PenColor = ConsoleColor.Red };

            Action action1 = new Action(stu1.DoHomework);
            Action action2 = new Action(stu2.DoHomework);
            Action action3 = new Action(stu3.DoHomework);

            action1 += action2;
            action1 += action3;
            action1.Invoke();
        }
    }

    class Student
    {
        public string Name { get; set; }
        public ConsoleColor PenColor { get; set; }

        public void DoHomework()
        {
            for (int i = 0; i < 5; i++)
            {
                Console.ForegroundColor = this.PenColor;
                Console.WriteLine("Student {0} doing homework {1} hours", this.Name, i);
                Thread.Sleep(1000);
            }
        }

    }
}

委托的异步调用

同步调用:

  • 两段程序,一段在另一段执行完成的基础上再执行;
  • 同步调用是在同一线程内进行,即在单线程中串行调用。

异步调用:

  • 两段程序,同时执行,二者之间会相互抢占资源;
  • 异步调用是基于多线程的,即在多线程中并行调用。

在异步调用时,又有隐式多线程和显式多线程的区分:

  • 隐式异步调用:使用委托的 BeginInvoke();
    • 该方法会自动生成一个分支线程,然后在分支线程中调用封装的方法。
    • 该方法在 .net core 中不支持
  • 显式异步调用:使用Thread或Task,Thread是比较老的写法。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace CSharp_DelegateLearning_AsyncCall
{
    class Program
    {
        static void Main(string[] args)
        {
            Student stu1 = new Student() { Name = "zhao", PenColor = ConsoleColor.Green };
            Student stu2 = new Student() { Name = "qian", PenColor = ConsoleColor.Yellow };
            Student stu3 = new Student() { Name = "sun", PenColor = ConsoleColor.Red };

            /**隐式异步调用:该方法在 .net core 中不支持
            *此时三个线程会因为资源发生冲突,导致字体颜色多次变化
            *解决这些冲突通常需要给线程加锁。*/
            //Action action1 = new Action(stu1.DoHomework);
            //Action action2 = new Action(stu2.DoHomework);
            //Action action3 = new Action(stu3.DoHomework);
            //action1.BeginInvoke(null, null);
            //action2.BeginInvoke(null, null);
            //action3.BeginInvoke(null, null);
            
            //显示异步调用:
            //方法1:使用Thread,比较老的写法
            /*Thread thread1 = new Thread(new ThreadStart(stu1.DoHomework));
            Thread thread2 = new Thread(new ThreadStart(stu2.DoHomework));
            Thread thread3 = new Thread(new ThreadStart(stu3.DoHomework));
            thread1.Start();
            thread2.Start();
            thread3.Start();*/

            //方法2:
            Task task1 = new Task(new Action(stu1.DoHomework));
            Task task2 = new Task(new Action(stu2.DoHomework));
            Task task3 = new Task(new Action(stu3.DoHomework));
            task1.Start();
            task2.Start();
            task3.Start();

            for (int i = 0; i < 10; i++)
            {
                Console.ForegroundColor = ConsoleColor.White;
                Console.WriteLine("Main Thread {0}", i);
                Thread.Sleep(1000);
            }
        }
    }

    class Student
    {
        public string Name { get; set; }
        public ConsoleColor PenColor { get; set; }

        public void DoHomework()
        {
            for (int i = 0; i < 5; i++)
            {
                Console.ForegroundColor = this.PenColor;
                Console.WriteLine("Student {0} doing homework {1} hours", this.Name, i);
                Thread.Sleep(1000);
            }
        }

    }
}

使用接口取代委托

在对委托使用不当的时候,会降低代码的可读性,不利于维护。而使用接口则可以避免这些麻烦,且同样可以获得相应的功能。Java中就完全用接口取代了委托的功能。

using System;

namespace CSharp_DelegateLearning_InterfaceReplaceDelegate
{

    class Program
    {
        static void Main(string[] args)
        {
            IProductFactory pizzaFactory = new PizzaFactory();
            IProductFactory toyCarFactory = new ToyCarFactory();
            WrapFactory wrapFactory = new WrapFactory();

            Box box1 = wrapFactory.WrapProduct(pizzaFactory);
            Box box2 = wrapFactory.WrapProduct(toyCarFactory);

            Console.WriteLine(box1.Product.Name);
            Console.WriteLine(box2.Product.Name);
        }
    }

    interface IProductFactory
    {
        Product MakeProduct();
    }

    class PizzaFactory : IProductFactory
    {
        public Product MakeProduct()
        {
            Product pizza = new Product();
            pizza.Name = "Pizza";
            return pizza;
        }
    }
    class ToyCarFactory : IProductFactory
    {
        public Product MakeProduct()
        {
            Product toyCar = new Product();
            toyCar.Name = "Toy Car";
            return toyCar;
        }
    }

    class Product
    {
        public string Name { get; set; }
    }

    class Box
    {
        public Product Product { get; set; }
    }

    class WrapFactory
    {
        public Box WrapProduct(IProductFactory productFactory)
        {
            Box box = new Box();
            Product product = productFactory.MakeProduct();
            box.Product = product;
            return box;
        }
    }
}

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值