通过 刘铁锰老师的 C# 入门详解中委托详解 记录学习,讲得真的非常好
程序 = 数据结构 + 算法。
数据结构也就是存放 数据 的结构,比如最常用的数组、哈希表、链表等。
算法我目前的理解是尽量减少一个 方法 的时间复杂度,比如排序算法中冒泡排序的时间复杂度为 O(N^2),而快排的时间复杂度为O(log2n),显然,冒泡排序的时间复杂度高于快排。
跑题了。。。
数据 和 方法 存储在什么地方呢?
答案是内存中。
数据 存储在以某个地址为起点的一段内存
方法 存储在以某个地址为起点的一段内存中所存储的一组机器语言指令
方法的调用 分为 直接调用 和 间接调用
直接调用:通过方法名调用,CPU 通过读取函数名从而获得函数所在地址 → 执行函数 → 返回
间接调用:通过函数指针调用,CPU 通过读取函数指针存储的函数所在地址 → 执行函数 → 返回
委托就是 间接调用方法,也就是说,将函数作为参数传递,在合适的时候调用该方法
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);
}
}
}
委托与类同级,也是一种数据类型,因此声明时一般都是声明在以类同级的地方
代码如下
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;
}
}
委托可用于 模板方法 和 回调方法
什么是模板方法?
来自网络: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() 里添加新的产品即可。
以下是刘铁锰老师对委托中模板方法的总结
- 相当于填空题
- 常位于代码中部
- 委托有返回值
什么是回调方法?
来自网络:C 语言回调函数详解
解释: 函数 F1 调用函数 F2 的时候,函数 F1 通过参数给 函数 F2 传递了另外一个函数 F3 的指针,在函数 F2 执行的过程中,函数F2 调用了函数 F3,这个动作就叫做回调(Callback),而先被当做指针传入、后面又被回调的函数 F3 就是回调函数。
虽然说的是 C 语言的回调函数,但是对应 C# 的回调方法道理也是一样的
代码如下
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;
}
}
}
以下是刘铁锰老师对委托中回调方法的总结
- 相当于 “流水线”
- 常位于代码末尾
- 委托无返回值
刘铁锰老师对于委托的整体优缺点总结
难精通 + 易使用 + 功能强大,一旦滥用后果严重
- 方法级别的紧耦合,工作中慎用
- 可读性下降,debug 难度增加
- 把委托回调、异步调用和多线程纠缠在一起,会让项目难以维护
- 使用不当可能会造成内存泄漏和程序性能下降
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);
}
}
}
}
什么是 异步调用 ?
个人理解:
先来说 异步,编程中异步指的是同时进行,也就是宏观意义上的程序的并行(不是同步),而 同步 指的是程序的串行。
调用 指的是调用某个回调方法。
结合博客内容来理解就是 开辟多线程完成某个委托,在代码上看不出来开辟了多线程。
之前写过一篇 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);
}
}
}
}
对 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;
}
}
}
目前的理解为:
2022.08.29,后续学完事件再看看有啥新的理解。