C# 委托详解

通过 刘铁锰老师的 C# 入门详解中委托详解 记录学习,讲得真的非常好

1. 什么是委托

1.1 一切皆地址

程序 = 数据结构 + 算法。
数据结构也就是存放 数据 的结构,比如最常用的数组、哈希表、链表等。
算法我目前的理解是尽量减少一个 方法 的时间复杂度,比如排序算法中冒泡排序的时间复杂度为 O(N^2),而快排的时间复杂度为O(log2n),显然,冒泡排序的时间复杂度高于快排。
跑题了。。。
数据方法 存储在什么地方呢?

答案是内存中。

数据 存储在以某个地址为起点的一段内存
方法 存储在以某个地址为起点的一段内存中所存储的一组机器语言指令

1.2 方法的调用

方法的调用 分为 直接调用间接调用
直接调用:通过方法名调用,CPU 通过读取函数名从而获得函数所在地址 → 执行函数 → 返回
间接调用:通过函数指针调用,CPU 通过读取函数指针存储的函数所在地址 → 执行函数 → 返回

1.3 什么是委托

委托就是 间接调用方法,也就是说,将函数作为参数传递,在合适的时候调用该方法

C# 中内置了两个常用委托类型,

无返回值有 0 - 16 个参数的 Action

使用方法:
在这里插入图片描述

有返回值有 0 - 16 个参数的 Func

使用方法:
在这里插入图片描述

代码如下

using System;

namespace DelegateTest
{
    internal class Program
    {
        static void Main(string[] args)
        {
			Calculator calculator = new Calculator();
            // 2 C# 内置委托类型 Action
            // Action 类型委托没有返回值、有 0 - 16 个参数
            // 传入方法名, 注意不是方法

            // 2.1 使用 Action 无参数
            Console.Write("通过 C# 内置委托 Action<> 间接调用 calculator.Report, 结果为:  ");
            Action actionReport = new Action(calculator.Report);
            actionReport.Invoke();

            // 1.1 通过函数名调用
            Console.Write("直接调用 calculator.Report, 结果为: ");
            calculator.Report();

            Console.WriteLine();

            // 2.2 使用 Action 有 1 个参数
            Console.Write("通过 C# 内置委托 Action<> 间接调用 calculator.Log, 结果为:  ");
            string now = DateTime.Now.ToString();
            Action<string> actionLog = new Action<string>(calculator.Log);
            actionLog.Invoke(now);

            // 1.2 直接调用
            Console.Write("直接调用 calculator.Report, 结果为: ");
            calculator.Log(now);

            Console.WriteLine();
            
            int num1 = 100;
            int num2 = 200;

            // 3 C# 内置委托类型 Func
            // Func 中重载方法 17 种, 但是都是必须要有返回值, 第一个参数始终为返回值类型, 之后参数个数从 0 - 16

            // 3.1 有参数有返回值
            // Func<返回值类型, 参数 1 类型, 参数 2 类型> 实例化对象 = new Func<返回值类型, 参数 1 类型, 参数 2 类型>(需要调用方法名);
            Func<double, double, double> funcAdd = new Func<double, double, double>(calculator.Add);
            double resAdd = funcAdd.Invoke(num1, num2);
            Console.WriteLine("通过 C# 内置委托 Func<> 间接调用 calculator.Add, 结果为: {0}", resAdd);

            // 1.3 直接调用 calculator.Add
            resAdd = calculator.Add(num1, num2);
            Console.WriteLine("直接调用 calculator.Add, 结果为: {0}", resAdd);

            Console.WriteLine();

            // 3.2 间接调用 calculator.Sub
            Func<double, double, double> funcSub = new Func<double, double, double>(calculator.Sub);
            double resSub = funcSub.Invoke(num1, num2);
            Console.WriteLine("通过 C# 内置委托 Func<> 间接调用 calculator.Sub, 结果为: {0}", resSub);

            // 1.4 直接调用 calculator.Sub
            resSub = calculator.Sub(num1, num2);
            Console.WriteLine("直接调用 calculator.Sub, 结果为: {0}", resSub);

            Console.WriteLine();

            // 3.3 有返回值无参数
            Func<string> funcGetTime = new Func<string>(calculator.GetTime);
            string resGetTime = funcGetTime.Invoke();
            Console.WriteLine("通过 C# 内置委托 Func<> 间接调用 calculator.GetTime, 结果为: {0}", resGetTime);

            // 1.5 直接调用 calculator.GetTime
            resGetTime = calculator.GetTime();
            Console.WriteLine("直接调用 calculator.GetTime, 结果为: {0}", resGetTime);

            Console.WriteLine();
        }
    }

    class Calculator
    {
        public void Report()
        {
            Console.WriteLine("I have 3 methods.");
        }
        public double Add(double num1, double num2)
        {
            return num1 + num2;
        }

        public double Sub(double num1, double num2)
        {
            return num1 - num2;
        }

        public string GetTime()
        {
            string time = DateTime.Now.ToString();
            return time;
        }

        public void Log(string time)
        {
            Console.WriteLine("Now is {0}, I will record something to text ", time);
        }
    }
}

运行截图如下
在这里插入图片描述

2 如何自定义委托

委托与类同级,也是一种数据类型,因此声明时一般都是声明在以类同级的地方
在这里插入图片描述

声明方式如下
在这里插入图片描述
使用方式如下

在这里插入图片描述

代码如下

using System;

namespace DelegateTest
{
    internal class Program
    {
        static void Main(String[] args)
        {
            double num3 = 100;
            double num4 = 200;
            Calculator calculator = new Calculator();

            // 自定义委托类型 实例化对象 = new 自定义委托类型(需要调用对象的方法名);

            Calc calcAdd = new Calc(calculator.Add);
            double resAdd = calcAdd.Invoke(num3, num4);
            Console.WriteLine("通过自定义委托 Calc 间接调用 calculator.Add, 结果为:{0}", resAdd);
            resAdd = calculator.Add(num3, num4);
            Console.WriteLine("直接调用 calculator.Add, 结果为: {0}", resAdd);

            Console.WriteLine();

            Calc calcSub = new Calc(calculator.Sub);
            double resSub = calcSub.Invoke(num3,num4);
            Console.WriteLine("通过自定义委托 Calc 间接调用 calculator.Sub, 结果为:{0}", resSub);
            resSub = calculator.Sub(num3,num4);
            Console.WriteLine("直接调用 calculator.Sub, 结果为: {0}", resSub);

            Console.WriteLine();
        }
    }


    public delegate double Calc(double num1, double num2);

    class Calculator
    {
        public double Add(double num1, double num2)
        {
            return num1 + num2;
        }

        public double Sub(double num1, double num2)
        {
            return num1 - num2;
        }
}

结果如下
在这里插入图片描述

3 为什么使用委托

委托可用于 模板方法回调方法

3.1 模板方法

什么是模板方法?
来自网络:Java设计模式之(十三)——模板方法模式
定义:一个操作中的算法的框架, 而将一些步骤延迟到子类中。 使得子类可以不改
说人话:父类模板方法定义不变的流程,子类重写流程中的方法。

如下,产品工厂生产产品,包装工厂包装产品
在这里插入图片描述

namespace DelegateTest
{
    internal class Program
    {
        static void Main(string[] args)
        {
            // 产品工厂生产产品
            ProductFactory productFactory = new ProductFactory();
            
            Func<Product> funcBeer = new Func<Product>(productFactory.MakeBeer);
            Func<Product> funcCola = new Func<Product>(productFactory.MakeCola);
            
            // 包装工厂包装产品
            WarpFactory warpFactory = new WarpFactory();
            Box boxBeer = warpFactory.WarpProduct(funcBeer);
            Console.WriteLine(boxBeer.Product.Name);
            Box boxCola = warpFactory.WarpProduct(funcCola);
            Console.WriteLine(boxCola.Product.Name);
        }
    }
        class Product
    {
        public string Name { get; set; }
    }
    class Box
    {
        public Product Product { get; set; }
    }

    class WarpFactory
    {
        public Box WarpProduct(Func<Product> getProduct)
        {
            Box box = new Box();
            Product product = getProduct.Invoke();
            box.Product = product;
            return box;
        }
    }

    class ProductFactory
    {
        public Product MakeBeer()
        {
            Product product = new Product();
            product.Name = "Beer";
            return product;
        }

        public Product MakeCola()
        {
            Product product = new Product();
            product.Name = "Cola";
            return product;
        }
    }
}

运行结果如下
在这里插入图片描述

使用委托的目的则是在于 假如需要生产新的产品,只需要在 ProductFactory() 里添加新的产品即可

以下是刘铁锰老师对委托中模板方法的总结

  1. 相当于填空题
  2. 常位于代码中部
  3. 委托有返回值

3.2 回调方法

什么是回调方法?
来自网络:C 语言回调函数详解
解释: 函数 F1 调用函数 F2 的时候,函数 F1 通过参数给 函数 F2 传递了另外一个函数 F3 的指针,在函数 F2 执行的过程中,函数F2 调用了函数 F3,这个动作就叫做回调(Callback),而先被当做指针传入、后面又被回调的函数 F3 就是回调函数。

虽然说的是 C 语言的回调函数,但是对应 C# 的回调方法道理也是一样的

继续应该上面的例子,当产品的价格大于 5 时,记录下来
在这里插入图片描述

代码如下

using System;

namespace DelegateTest
{
	class Program{
		static class Main(String[] args) {
			// 生产工厂
		    ProductFactory productFactory = new ProductFactory();
		    
		    // 日志
		    Logger logger = new Logger();
		    Func<Product> funcBeer = new Func<Product>(productFactory.MakeBeer);
		    Func<Product> funcCola = new Func<Product>(productFactory.MakeCola);
		    Action<Product> funcLog = new Action<Product>(logger.Log);
			
			// 包装工厂
		    WarpFactory warpFactory = new WarpFactory();
		    Box boxBeer = warpFactory.WarpProduct(funcBeer,funcLog);
		    Console.WriteLine(boxBeer.Product.Name);
		    Box boxCola = warpFactory.WarpProduct(funcCola,funcLog);
		    Console.WriteLine(boxCola.Product.Name);
		    Console.WriteLine();
		}
	}
    class Logger
    {
        public void Log(Product product)
        {
            Console.WriteLine("Product '{0}' created at {1}. Price is {2}", product.Name, DateTime.Now.ToString(), product.Price);
        }
    }
    class Product
    {
        public string Name { get; set; }
        public double Price { get; set; }
    }
    class Box
    {
        public Product Product { get; set; }
    }

    class WarpFactory
    {
        public Box WarpProduct(Func<Product> getProduct, Action<Product> logCallback)
        {
            Box box = new Box();
            Product product = getProduct.Invoke();
            box.Product = product;
            if (product.Price >= 5)
            {
                logCallback.Invoke(product);
            }
            return box;
        }
    }

    class ProductFactory
    {
        public Product MakeBeer()
        {
            Product product = new Product();
            product.Name = "Beer";
            product.Price = 6;
            return product;
        }

        public Product MakeCola()
        {
            Product product = new Product();
            product.Name = "Cola";
            product.Price = 3;
            return product;
        }
    }
}

运行结果如下
在这里插入图片描述

以下是刘铁锰老师对委托中回调方法的总结

  1. 相当于 “流水线”
  2. 常位于代码末尾
  3. 委托无返回值

刘铁锰老师对于委托的整体优缺点总结
难精通 + 易使用 + 功能强大,一旦滥用后果严重

  1. 方法级别的紧耦合,工作中慎用
  2. 可读性下降,debug 难度增加
  3. 把委托回调、异步调用和多线程纠缠在一起,会让项目难以维护
  4. 使用不当可能会造成内存泄漏和程序性能下降

4 多播委托

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace DelegateTest
{
	class Progarm
	{
		static void Main(String[] args) 
		}
            Student stu1 = new Student() { id=1,penColor=ConsoleColor.Red};
            Student stu2 = new Student() { id=2,penColor=ConsoleColor.Green};
            Student stu3 = new Student() { id=3,penColor=ConsoleColor.Blue};
            Action action1 = new Action(stu1.DoHomework);
            Action action2 = new Action(stu2.DoHomework);
            Action action3 = new Action(stu3.DoHomework);

            //action1.Invoke();
            //action2.Invoke();
            //action3.Invoke();
			
			// 使用 += 实现多播委托
            action1 += action2;
            action1 += action3;
            action1.Invoke();
        }
	}
    class Student
    {
        public int id { 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.id, i);
                Thread.Sleep(1000);
            }
        }
    }
}

运行结果如下
在这里插入图片描述

5 隐式异步调用

什么是 异步调用
个人理解:
先来说 异步,编程中异步指的是同时进行,也就是宏观意义上的程序的并行(不是同步),而 同步 指的是程序的串行。
调用 指的是调用某个回调方法。
结合博客内容来理解就是 开辟多线程完成某个委托,在代码上看不出来开辟了多线程
之前写过一篇 C# 异步编程 的博客,欢迎访问

使用 委托的 BeginInvoke 方法实现 隐士异步调用

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace DelegateTest
{
	class Progarm
	{
		static void Main(String[] args) 
		}
            Student stu1 = new Student() { id=1,penColor=ConsoleColor.Red};
            Student stu2 = new Student() { id=2,penColor=ConsoleColor.Green};
            Student stu3 = new Student() { id=3,penColor=ConsoleColor.Blue};
            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);
        }
	}
    class Student
    {
        public int id { 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.id, i);
                Thread.Sleep(1000);
            }
        }
    }
}

执行结果如下:
在这里插入图片描述
可以看出,代码执行顺序不是顺序的。

6 适当的使用接口替代委托

对 3.1 中模板方法示例代码使用接口进行重构

代码如下

using System;

namespace DelegateTest
{
	class Program
	{
		static void Main(string[] args) {
			// 产品制造工厂
            IProductFactory makeBeer = new MakeBeer();
            IProductFactory MakeCalo = new MakeCalo();
			
			// 包装产品工厂
            WarpFactory warpFactory = new WarpFactory();
            Box resBeer = warpFactory.WarpProduct(makeBeer.Make());
            Console.WriteLine(resBeer.Product.Name);
            Box resCalo = warpFactory.WarpProduct(MakeCalo.Make());
            Console.WriteLine(resCalo.Product.Name);
		}
	}
    class Product
    {
        public string Name { get; set; }
        public double Price { get; set; }
    }
    class Box
    {
        public Product Product { get; set; }
    }

    class WarpFactory
    {
        // 接口取代委托
        public Box WarpProduct(Product product)
        {
            Box box = new Box();
            box.Product = product;
            return box;
        }
    }

    interface IProductFactory
    {
        Product Make();
    }

    class MakeBeer : IProductFactory
    {
        public Product Make()
        {
            Product product = new Product();
            product.Name = "Beer";
            product.Price = 6;
            return product;
        }
    }

    class MakeCalo : IProductFactory
    {
        public Product Make()
        {
            Product product = new Product();
            product.Name = "Calo";
            product.Price = 3;
            return product;
        }
    }
}

执行结果如下
在这里插入图片描述

7 个人总结(接口和委托)

使用接口逻辑如下
在这里插入图片描述

使用委托逻辑如下
在这里插入图片描述

目前的理解为:

  1. 从上面两张图可以看出,接口可能更 “粗”,委托更 “细”,以上面为例子,接口判断生产那种产品是在 Main 函数也就是主线程中,而委托判断生产那种产品则是在类的某个方法中;
  2. 接口不能用方法作为参数,而委托可以以方法名作为参数,从而实现动态调用。

2022.08.29,后续学完事件再看看有啥新的理解。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值