何为委托

  • 委托(delegate)是函数指针的”升级版”
    • 示例:C/C++中的函数指针 ```c

      include

//定义一个函数指针数据类型 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;

  1. //直接调用
  2. z = Add(x, y);
  3. printf("%d+%d=%d\n",x,y,z);
  4. z = Sub(x, y);
  5. printf("%d+%d=%d\n",x,y,z);
  6. //声明函数指针类型的变量
  7. Calc funcPoint1 = &Add;
  8. Calc funcPoint2 = ⋐
  9. //通过函数指针间接调用
  10. zz = funcPoint1(x, y);
  11. printf("%d+%d=%d\n",x,y,z);
  12. z = funcPoint2(x, y);
  13. printf("%d+%d=%d\n",x,y,z);
  14. system("pause");
  15. return 0;

}


- 一切皆地址
   - 变量(数据)是以某个地址为起点的一段内存中所存储的值
   - 函数(算法)是以某个地址为起点的一段内存中所存储的一组机器语言指令
- 直接调用与间接调用
   - 直接调用:通过函数名来调用函数,CPU通过函数名直接获得函数所在地址并开始执行→返回
   - 间接调用:通过函数指针来调用函数,CPU通过读取函数指针存储的值获得函数所在地址并开始执行→返回
- Java中没有与委托相对应的功能实体
- 委托的简单使用
   - Action委托:调用没有返回值的函数
   - Func委托:调用有返回值的函数
```csharp
using System;

namespace DelegateExample
{
    class Program
    {
        static void Main(string[] args)
        {
            Calculator cal = new Calculator();
            Action action = new Action(cal.Report);//创建Action委托
            cal.Report();//函数的直接调用
            action.Invoke();//间接调用
            action();//间接调用的简易写法

            Func<int, int, int> funcAdd = new Func<int, int, int>(cal.Add);//创建Func委托
            Func<int, int, int> funcSub = new Func<int, int, int>(cal.Sub);

            int x = 100, y = 200, z = 0;
            z = funcAdd.Invoke(x,y);
            Console.WriteLine(z);
            z = funcSub(x, y);
            Console.WriteLine(z);
        }
    }

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

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

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

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

  • 委托是一种类(class),类是数据类型所以委托也是一种数据类型
  • 它的声明方式与一般的类不同,主要是为了照顾可读性和C/C++传统
  • 注意声明委托的位置
    • 避免写错地方结果声明成嵌套类型
  • 委托与所封装的方法必须”类型兼容”
  • 声明格式如下:

image.png

  • 示例: ```csharp using System;

namespace DelegateExample { //声明自定义委托 public delegate double Calc(double x, double y);

class Program
{
    static void Main(string[] args)
    {
        Calculator calculator = new Calculator();
        Calc calc1 = new Calc(calculator.Add);
        Calc calc2 = new Calc(calculator.Sub);
        Calc calc3 = new Calc(calculator.Mul);
        Calc calc4 = new Calc(calculator.Div);

        double a = 100, b = 200, c = 0;
        c = calc1(a, b);
        Console.WriteLine(c);
        c = calc2(a, b);
        Console.WriteLine(c);
        c = calc3(a, b);
        Console.WriteLine(c); 
        c = calc4(a, b);
        Console.WriteLine(c);
    }
}

class Calculator
{
    public double Add(double a, double b)
    {
        return a + b;
    }

    public double Sub(double a, double b)
    {
        return a - b;
    }

    public double Mul(double a, double b)
    {
        return a * b;
    }

    public double Div(double a, double b)
    {
        return a / b;
    }
}

}


<a name="hNXTb"></a>
# 委托的一般使用

- 委托的一般使用:把方法当做参数传给另一个方法

<a name="qSIkD"></a>
## 模板方法

- 正确使用1:模板方法,"借用"指定的外部方法来产生结果
   - 相当于"填空题"
   - 常位于代码中部
   - 委托有返回值
   - 示例:模板方法的好处是,一旦写好后,Product类,Box类和WrapFactory类都无需改动,只需改变ProductFactory类,通过增加多个产品方法,最大限度地实现代码的复用。

代码的Reuse复用,不但可以提高工作效率,还可以减少bug的引入,良好的复用结构是所有优秀软件所追求的共同目标之一。
```csharp
using System;

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

            //创建Func委托,返回值类型为Product,参数为空
            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);//输出:Pizza
            Console.WriteLine(box2.Product.Name);//输出:ToyCar
        }
    }

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

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

    class WrapFactory
    {
        //WrapProduct方法是一个模板方法,它的参数是委托类
        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 = "ToyCar";
            return product;
        }
    }
}

回调方法(callback)

  • 正确使用2:回调(callback)方法,调用指定的外部方法
    • 相当于”流水线”
    • 常位于代码尾部
    • 委托无返回值
    • 示例:日常工作中对委托的常规用法 ```csharp using System;

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

        //创建Func委托,返回值类型为Product,参数为空
        Func<Product> func1 = new Func<Product>(productFactory.MakePizza);
        Func<Product> func2 = new Func<Product>(productFactory.MakeToyCar);

        Logger logger = new Logger();
        //创建Action委托,参数类型为Product,无返回值
        Action<Product> log = new Action<Product>(logger.Log);

        //调用模板方法
        Box box1 = wrapFactory.WrapProduct(func1, log);//输出:Product ToyCar created at 2021/6/12 8:48:49.Price is 100
        Box box2 = wrapFactory.WrapProduct(func2, log);

        Console.WriteLine(box1.Product.Name);//输出:Pizza
        Console.WriteLine(box2.Product.Name);//输出:ToyCar
    }
}

class Logger
{
    public void Log(Product product)
    {
        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
{
    //WrapProduct方法是一个模板方法和回调方法,它的参数是委托类
    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 product = new Product();
        product.Name = "Pizza";
        product.Price = 12;
        return product;
    }

    public Product MakeToyCar()
    {
        Product product = new Product();
        product.Name = "ToyCar";
        product.Price = 100;
        return product;
    }
}

}


- 注意:难精通+易使用+功能强大的东西,一旦被滥用则后果非常严重
   - 缺点1:这是一种方法级别的紧耦合,现实工作中要慎之又慎
   - 缺点2:使可读性下降,debug的难度增加
   - 缺点3:把委托回调,异步调用和多线程纠缠在一起,会让代码变得难以阅读和维护
   - 缺点4:委托使用不当有可能造成内存泄漏和程序性能下降

<a name="NXrTE"></a>
# 委托的高级使用

<a name="TguUP"></a>
## 多播委托

- 定义:用一个委托封装多个方法的使用方式
- 示例:
```csharp
using System;
using System.Threading;

namespace MulticastDelegateExample
{
    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();
            action2();
            action3();

            //以下为多播委托:将action2和action3都封装到action1中
            action1 += action2;
            action1 += action3;
            action1();
        }
    }

    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 {this.ID} doing homework {i} hours");
                Thread.Sleep(1000);
            }
        }
    }
}
  • 输出结果为:

image.png

隐式异步调用

  • 同步与异步的简介
    • 中英文的语言差异
    • 同步:你做完了我(在你的基础上)接着做
    • 异步:咱们两个同时做(相当于汉语中的”同步进行”)
  • 同步调用与异步调用的对比
    • 每一个运行的程序是一个进程(process)
    • 每个进程可以有一个或者多个线程(thread)
    • 同步调用是在同一线程内
    • 异步调用的底层机理是多线程
    • 串行==同步==单线程,并行==异步==多线程
  • 隐式多线程V.S.显式多线程

直接同步调用:使用方法名

  • 示例: ```csharp using System; using System.Threading;

namespace MulticastDelegateExample { 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 };

        //直接同步调用
        stu1.DoHomework();
        stu2.DoHomework();
        stu3.DoHomework();

        for (int 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 = this.PenColor;
            Console.WriteLine($"Student {this.ID} doing homework {i} hours");
            Thread.Sleep(1000);
        }
    }
}

}


- 输出结果:

![image.png](https://cdn.nlark.com/yuque/0/2021/png/21507654/1623835448134-73751533-d19d-4b4e-9de9-306be1bfc150.png#clientId=u4f3645af-6211-4&from=paste&height=288&id=u1bcaac99&margin=%5Bobject%20Object%5D&name=image.png&originHeight=576&originWidth=378&originalType=binary&ratio=2&size=41998&status=done&style=none&taskId=u409f496e-cf77-4322-b553-4e4f5865596&width=189)

<a name="VUtDw"></a>
### 间接同步调用:使用单播/多播委托的Invoke方法

<a name="Zg9Hy"></a>
### 隐式异步调用:使用委托的BeginInvoke

- 示例:
```csharp
using System;
using System.Threading;

namespace MulticastDelegateExample
{
    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.BeginInvoke(null,null);
            action2.BeginInvoke(null,null);
            action3.BeginInvoke(null,null);

            for (int 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 = this.PenColor;
                Console.WriteLine($"Student {this.ID} doing homework {i} hours");
                Thread.Sleep(1000);
            }
        }
    }
}
  • 输出结果:

image.png

  • 分析结果发现:Main线程、action1和action3线程都是红色,颜色出错。是因为这3个线程都在访问Console的ForegroundColor属性,当多个线程共享(即同时访问)同一个资源(比如某个变量)的情况,这时如果处理不当就会产生线程间争夺资源的冲突(可用线程锁lock解决)

显式异步调用:使用Thread或Task

  • 示例:使用thread ```csharp using System; using System.Threading;

namespace MulticastDelegateExample { 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 };

        Thread thread1 = new Thread(new ThreadStart(stu1.DoHomework));
        Thread thread2 = new Thread(new ThreadStart(stu2.DoHomework));
        Thread thread3 = new Thread(new ThreadStart(stu3.DoHomework));

        //显式异步调用thread
        thread1.Start();
        thread2.Start();
        thread3.Start();

        for (int 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 = this.PenColor;
            Console.WriteLine($"Student {this.ID} doing homework {i} hours");
            Thread.Sleep(1000);
        }
    }
}

}


- 输出结果:

![image.png](https://cdn.nlark.com/yuque/0/2021/png/21507654/1623836223188-935ef816-8c2f-4c5b-b999-cc73df44915b.png#clientId=u4f3645af-6211-4&from=paste&height=290&id=ufe5c9d00&margin=%5Bobject%20Object%5D&name=image.png&originHeight=579&originWidth=362&originalType=binary&ratio=2&size=48204&status=done&style=none&taskId=u1da08116-f0d5-445b-8792-dc832320159&width=181)

- 示例:使用task(更为高级),结果一致
```csharp
using System;
using System.Threading;
using System.Threading.Tasks;

namespace MulticastDelegateExample
{
    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 };

            Task task1 = new Task(new Action(stu1.DoHomework));
            Task task2 = new Task(new Action(stu2.DoHomework));
            Task task3 = new Task(new Action(stu3.DoHomework));

            //显式异步调用task
            task1.Start();
            task2.Start();
            task3.Start();

            for (int 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 = this.PenColor;
                Console.WriteLine($"Student {this.ID} doing homework {i} hours");
                Thread.Sleep(1000);
            }
        }
    }
}

应该适时地使用接口(interface)取代一些对委托的使用

  • Java完全地使用接口取代了委托的功能,即Java没有与C#中委托相对应的功能实体
  • 示例:使用接口重构前面的模板方法案例: ```csharp using System;

namespace DelegateExample { 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);//输出:Pizza
        Console.WriteLine(box2.Product.Name);//输出:ToyCar
    }
}

interface IProductFactory
{
    Product Make();
}

class PizzaFactory : IProductFactory
{
    public Product Make()
    {
        Product product = new Product();
        product.Name = "Pizza";
        return product;
    }
}

class ToyCarFactory : IProductFactory
{
    public Product Make()
    {
        Product product = new Product();
        product.Name = "ToyCar";
        return product;
    }
}
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.Make();
        box.Product = product;
        return box;
    }
}

} ```