# 定义

  • 委托(delegate)是函数指针的 “升级版”
    • 示例:C/C++ 中的函数指针
  • 一切皆地址
    • 变量(数据)是以某个地址为起点的一段内存中所存储的值
    • 函数(算法)是以某个地址为起点的一段内存中所存储的一组机器语言指令
  • 直接调用与间接调用
    • 直接调用:通过函数名来调用函数,CPU 通过函数名直接获得函数所在地址并开始执行 ➡ 返回
    • 间接调用:通过函数指针来调用函数,CPU 通过读取函数指针存储的值获得函数所在地址并开始执行 ➡ 返回
  • Java 中没有与委托相对应的功能实体
  • 委托的简单使用
    • Action 委托
    • Func 委托

# C 语言函数指针

直接调用
#include <stdio.h>
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 = 200;
    int z = 0;
    
    z = Add(x, y);
    printf("%d+%d=%d\n", x, y, z); 
    z = Sub(x, y);
    printf("%d+%d=%d\n", x, y, z);
    
    system("pause");
    return 0;
}
间接调用:函数指针
#include <stdio.h>
typedef int(* Calc)(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 = 200;
    int z = 0;
    
    Calc funcPoint1 = &Add; // 声明函数指针类型的变量
    Calc funcPoint2 = &Sub;
    
    z = funcPoint1(x, y);
    printf("%d+%d=%d\n", x, y, z);
    
    z = funcPoint2(x, y);
    printf("%d+%d=%d\n", x, y, z);
    
    system("pause");
    return 0;
}

# Java

Java 语言由 C++ 发展而来,为了提高应用安全性,Java 语言禁止程序员直接访问内存地址

即 Java 语言把 C++ 中所有与指针相关的内容都舍弃掉了

# 委托实例 Action 与 Func

Action 和 Func 是 C# 内置的委托实例,它们都有很多重载以方便使用

示例:Action
class Program
{
    static void Main(string[] args)
    {
        Calculator calculator = new Calculator();
        calculator.Report(); // 直接调用
        
        Action action = new Action(calculator.Report); // Action 用于无形参无返回值的方法
        action.Invoke(); // 间接调用
        action(); // 模仿函数指针的简略写法
    }
}
class Calculator
{
    public void Report()
    {
        Console.WriteLine("I have 3 methods.");
    }
    public int Add(int a, int b)
    {
        return a + b;
    }
    public int Sub(int a, int b)
    {
        return a - b;
    }
}
示例:Func
class Program
{
    static void Main(string[] args)
    {
        Calculator calculator = new Calculator();
        Func<int, int, int> func1 = new Func<int, int, int>(calculator.Add);
        Func<int, int, int> func2 = new Func<int, int, int>(calculator.Sub);
        int x = 100;
        int y = 200;
        int z = 0;
        z = func1.Invoke(x, y);
        Console.WriteLine(z);
        z = func2.Invoke(x, y);
        Console.WriteLine(z);
        // Func 也有简略写法
        z = func1(x, y);
        z = func2(x, y);
    }
}
class Calculator
{
    public void Report()
    {
        Console.WriteLine("I have 3 methods.");
    }
    public int Add(int a, int b)
    {
        return a + b;
    }
    public int Sub(int a, int b)
    {
        return a - b;
    }
}

# 声明(自定义委托)

  • 委托是一种类(class),类是数据类型,所以委托也是一种数据类型
  • 声明方式与一般的类不同,主要是为了照顾可读性和 C/C++ 传统
  • 注意声明委托的位置
    • 避免写错地方结果声明成嵌套类型
  • 委托与所封装的方法必须 “类型兼容”
    • 返回值的数据类型一致
    • 参数列表在个数和数据类型上一致(参数名不需要一样)

委托是类,所以声明位置是和 class 处于同一个级别。但 C# 允许嵌套声明类(一个类里面可以声明另一个类),所以有时也会有 delegate 在 class 内部声明的情况

static void Main(string[] args)
{
    Type t = typeof(Action);
    Console.WriteLine(t.IsClass); // True
}
示例
public delegate double Calc(double x, double y);
class Program
{
    static void Main(string[] args)
    {
        Calculator calculator = new Calculator();
        Calc calc1 = new Calc(calculator.Mul);
        Console.WriteLine(calc1(5, 6));
    }
}
class Calculator
{
    public double Mul(double x, double y)
    {
        return x * y;
    }
    public double Div(double x, double y)
    {
        return x / y;
    }
}

# 一般使用

把方法当作参数传给另一个方法

  • 模版方法:“借用” 指定的外部方法来产生结果
    • 相当于 “填空题”
    • 常位于代码中部
    • 委托有返回值
  • 回调(callback)方法:调用指定的外部方法
    • 相当于 “流水线”
    • 常位于代码尾部
    • 委托无返回值

⚠️ 注意:难精通 + 易使用 + 功能强大东西,一旦被滥用则后果非常严重

缺点:

  • 这是一种方法级别的紧耦合,现实工作中要慎之又慎
  • 使可读性下降、debug 的难度增加
  • 把委托回调、异步调用和多线程纠缠在一起,会让代码变得难以阅读和维护
  • 委托使用不当有可能造成内存泄漏和程序性能下降

# 模板方法

利用模板方法,提高代码复用性

示例
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.MakeToyCar);
        Box box1 = wrapFactory.WrapProduct(func1);
        Box box2 = wrapFactory.WrapProduct(func2);
        Console.WriteLine(box1.Product.Name);
        Console.WriteLine(box2.Product.Name);
    }
}
class Product
{
    public string Name { get; set; }
}
class Box
{
    public Product Product { get; set; }
}
class WrapFactory
{
    public Box WrapProduct(Func<Product> getProduct)
    {
        Box box = new Box();
        Product product = getProduct.Invoke();
        box.Product = product;
        return box;
    }
}
class ProductFactory
{
    public Product MakePizza()
    {
        Product product = new Product();
        product.Name = "Pizza";
        return product;
    }
    public Product MakeToyCar()
    {
        Product product = new Product();
        product.Name = "Toy Car";
        return product;
    }
}
// 输出结果
Pizza
Toy Car

# 回调方法

通过委托类型参数传入主调方法的被调用方法,主调方法根据自己的逻辑决定是否调用这个方法

示例
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.MakeToyCar);
        Logger logger = new Logger();
        Action<Product> log = new Action<Product>(logger.Log); // Action 只有传入参数,所以此处以 Product 为参数
        Logger logger2 = new Logger();
        Box box1 = wrapFactory.WrapProduct(func1, log);
        Box box2 = wrapFactory.WrapProduct(func2, log);
        Console.WriteLine(box1.Product.Name);
        Console.WriteLine(box2.Product.Name);
    }
}
// 程序运行状态
class Logger
{
    public void Log(Product product)
    {
        // Now 是带时区的时间,存储到数据库应该用不带时区的时间 UtcNow
        Console.WriteLine($"Product {product.Name} created at {DateTime.UtcNow} , Price is {product.Price}");
    }
}
class Product
{
    public string Name { get; set; }
    public double Price { get; set; }
}
class Box
{
    public Product Product { get; set; }
}
class WrapFactory
{
    public Box WrapProduct(Func<Product> getProduct, Action<Product> logCallBack)
    {
        Box box = new Box();
        Product product = getProduct.Invoke();
        if (product.Price >= 50) { // 只 log 价格高于 50 的
            logCallBack(product);
        }
        box.Product = product;
        return box;
    }
}
class ProductFactory
{
    public Product MakePizza()
    {
        Product product = new Product();
        product.Name = "Pizza";
        product.Price = 12;
        return product;
    }
    public Product MakeToyCar()
    {
        Product product = new Product();
        product.Name = "Toy Car";
        product.Price = 100;
        return product;
    }
}
// 输出结果
Product Toy Car created at 2024/11/11 9:00:33 , Price is 100
Pizza
Toy Car

# 注意委托滥用

img

img

# 高级使用

img

# 多播(multicast)委托

多播委托即一个委托内部封装不止一个方法

示例
using System;
using System.Threading;
namespace DelegateExample
{
    class Program
    {
        static void Main(string[] args)
        {
            Student stu1 = new Student { ID = 1, PenColor = ConsoleColor.Yellow };
            Student stu2 = new Student { ID = 2, PenColor = ConsoleColor.Green };
            Student stu3 = new Student { ID = 3, PenColor = ConsoleColor.Red };
            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 = PenColor;
                Console.WriteLine($"Student {ID} doing homework {i} hour(s)");
                Thread.Sleep(1000);
            }
        }
    }
}

# 隐式异步调用

# 三种同步调用

示例
using System;
using System.Threading;
namespace DelegateExample
{
    class Program
    {
        static void Main(string[] args)
        {
            Student stu1 = new Student { ID = 1, PenColor = ConsoleColor.Yellow };
            Student stu2 = new Student { ID = 2, PenColor = ConsoleColor.Green };
            Student stu3 = new Student { ID = 3, PenColor = ConsoleColor.Red };
            // 1、直接同步调用
            // stu1.DoHomework();
            // stu2.DoHomework();
            // stu3.DoHomework();
            Action action1 = new Action(stu1.DoHomework);
            Action action2 = new Action(stu2.DoHomework);
            Action action3 = new Action(stu3.DoHomework);
            // 2、单播委托的间接同步调用
            // action1.Invoke();
            // action2.Invoke();
            // action3.Invoke();
            // 3、多播委托的间接同步调用
            action1 += action2;
            action1 += action3;
            action1.Invoke();
            // 主线程模拟在做某些事情
            for (var i = 0; i < 10; i++)
            {
                Console.ForegroundColor = ConsoleColor.Cyan;
                Console.WriteLine($"Main thread {i}");
                Thread.Sleep(1000);
            }
        }
    }
    class Student
    {
        public int ID { get; set; }
        public ConsoleColor PenColor { get; set; }
        public void DoHomework()
        {
            for (int i = 0; i < 5; i++)
            {
                Console.ForegroundColor = PenColor;
                Console.WriteLine($"Student {ID} doing homework {i} hour(s)");
                Thread.Sleep(1000);
            }
        }
    }
}

img

# 使用委托进行隐式异步调用 BeginInvoke

示例
using System;
using System.Threading;
namespace DelegateExample
{
    class Program
    {
        static void Main(string[] args)
        {
            Student stu1 = new Student { ID = 1, PenColor = ConsoleColor.Yellow };
            Student stu2 = new Student { ID = 2, PenColor = ConsoleColor.Green };
            Student stu3 = new Student { ID = 3, PenColor = ConsoleColor.Red };
            Action action1 = new Action(stu1.DoHomework);
            Action action2 = new Action(stu2.DoHomework);
            Action action3 = new Action(stu3.DoHomework);
            // 使用委托进行隐式异步调用
            // BeginInvoke 自动生成分支线程,并在分支线程内调用方法
            action1.BeginInvoke(null, null);
            action2.BeginInvoke(null, null);
            action3.BeginInvoke(null, null);
            // 主线程模拟在做某些事情
            for (var i = 0; i < 10; i++)
            {
                Console.ForegroundColor = ConsoleColor.Cyan;
                Console.WriteLine($"Main thread {i}");
                Thread.Sleep(1000);
            }
        }
    }
    class Student
    {
        public int ID { get; set; }
        public ConsoleColor PenColor { get; set; }
        public void DoHomework()
        {
            for (int i = 0; i < 5; i++)
            {
                Console.ForegroundColor = PenColor;
                Console.WriteLine($"Student {ID} doing homework {i} hour(s)");
                Thread.Sleep(1000);
            }
        }
    }
}

img

# 使用 Thread 与 Task 进行异步调用

示例
using System;
using System.Threading;
using System.Threading.Tasks;
namespace DelegateExample
{
    class Program
    {
        static void Main(string[] args)
        {
            Student stu1 = new Student { ID = 1, PenColor = ConsoleColor.Yellow };
            Student stu2 = new Student { ID = 2, PenColor = ConsoleColor.Green };
            Student stu3 = new Student { ID = 3, PenColor = ConsoleColor.Red };
            // 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
            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 (var i = 0; i < 10; i++)
            {
                Console.ForegroundColor = ConsoleColor.Cyan;
                Console.WriteLine($"Main thread {i}");
                Thread.Sleep(1000);
            }
        }
    }
    class Student
    {
        public int ID { get; set; }
        public ConsoleColor PenColor { get; set; }
        public void DoHomework()
        {
            for (int i = 0; i < 5; i++)
            {
                Console.ForegroundColor = PenColor;
                Console.WriteLine($"Student {ID} doing homework {i} hour(s)");
                Thread.Sleep(1000);
            }
        }
    }
}

image.png

# 适时地使用接口(interface)取代委托

示例
using System;
namespace DelegateExample
{
    class Program
    {
        static void Main(string[] args)
        {
            IProductFactory pizzaFactory = new PizzaFactory();
            IProductFactory toyCarFactory = new ToyCarFactory();
            var 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 Make();
    }
    class PizzaFactory : IProductFactory
    {
        public Product Make()
        {
            var product = new Product();
            product.Name = "Pizza";
            return product;
        }
    }
    class ToyCarFactory : IProductFactory
    {
        public Product Make()
        {
            var product = new Product();
            product.Name = "Toy Car";
            return product;
        }
    }
    class Product
    {
        public string Name { get; set; }
    }
    class Box
    {
        public Product Product { get; set; }
    }
    class WrapFactory
    {
        // 模板方法,提高复用性
        public Box WrapProduct(IProductFactory productFactory)
        {
            var box = new Box();
            Product product = productFactory.Make();
            box.Product = product;
            return box;
        }
    }
}
更新于

请我喝奶茶⌯oᴗo⌯

呆鸭 微信支付

微信支付

呆鸭 支付宝

支付宝