十、委托详解

委托详解

什么是委托

在这里插入图片描述

C/C++中的间接调用函数的示例:

#include<stdio.h>

/* 声明函数指针 
 * 并且将这种函数指针声明为一种类型 
 * 使用函数指针,间接调用函数
 */
typedef int (*Calc)(int a, int b);


int Add(int a, int b){
	int res = a + b;
	return res;
}
int Sub(int a, int b){
	int res = a - b;
	return res;
}

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);
	return 0;
}

C#类库中自带的委托示例:

namespace DelegateExample {
    internal class Program {
        static void Main(string[] args) {
            Calculator calculator = new Calculator();
            //Action委托适用于无返回类型的方法
            Action action = new Action(calculator.Report);
            //直接调用Report方法
            calculator.Report();
            //间接调用
            action.Invoke();
            //模仿函数指针的形式
            action();

            //Func委托适用于有返回类型的方法
            //前面两个int 是参数
            //最后一个int 是返回类型
            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);
        }
    }
    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;
        }
    }
}

在这里插入图片描述

委托的特性:

  • 引用方法: 委托允许存储对方法的引用,使得方法可以被动态地调用。
  • 类型安全: 委托是类型安全的,它们在编译时会检查方法签名,确保委托实例只能引用与其声明的相同签名的方法。
  • 多播性: 委托支持多播,即一个委托实例可以引用多个方法。通过 +=-= 操作符,可以动态地添加或移除委托链中的方法。
  • 异步编程: 委托在异步编程中扮演了重要的角色,尤其是在使用 BeginInvokeEndInvoke 进行异步操作时。
  • 匿名方法和 Lambda 表达式: C# 支持使用匿名方法和 Lambda 表达式来创建简洁的委托实例,减少了样板代码的编写。
  • 委托泛型化: C# 提供了泛型委托 ActionFunc,分别用于表示没有返回值和有返回值的委托,减少了需要定义新委托类型的情况。

委托的优势

委托在调用方法是展现了很好的灵活性,

其次,可以使用委托来创建需要顺序调用的方法队列。而执行队列操作在服务中又非常常见,给我们带来更好的伸缩性。

另一个优势则在于允许多个操作并行执行,委托提供在不同线程上异步操作的内置支持,提高的响应能力。

但是,最重要的好处是,在实现事件时,在不了解彼此的不同对象之间发送消息

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

在这里插入图片描述

声明格式是仿照函数指针的格式

using System.Security.Claims;

namespace DelegateExample {

    public delegate double Calc(double x, double y);

    internal 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;
            double b = 200;
            double c = 0;

            c = calc1.Invoke(a, b);
            Console.WriteLine(c);
            c = calc2.Invoke(a, b);
            Console.WriteLine(c);
            c = calc3.Invoke(a, b);
            Console.WriteLine(c);
            c = calc4.Invoke(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;
        }
    }
}

委托对象的构造

委托对象通常可采用两种方式进行构造,一种是提供委托将封装的方法的名称,另一种是使用 lambda 表达式

声明了一个名为Callback的委托,该委托可以封装采用字符串作为参数并返回void的方法:

public delegate void Callback(string message);
  1. 提供方法名称:

    • 假设有一个名为DelegateMethod的方法,实例化委托如下:

      Callback handler = DelegateMethod;
      
      public static void DelegateMethod(string message)
      {
          Console.WriteLine(message);
      }
      
  2. 使用Lambda表达式:

    • 以下代码使用Lambda表达式实例化了相同的委托:

      Callback handler = (message) => Console.WriteLine(message);
      

这两种方式的作用都是相同的。

委托的使用

在这里插入图片描述

示例:

模版方法和回调方法:

都是用委托类型的参数,封装一个外部的方法,然后把这个方法传进另一个方法的内部,再进行间接调用。

这就是委托的常规用法

using System.Security.Claims;

namespace DelegateExample {

    public delegate double Calc(double x, double y);

    internal class Program {
        static void Main(string[] args) {
            ProductFactory productFactory = new ProductFactory();
            WrapFactory wrapFactory = new WrapFactory();
            Logger logger = new Logger();

            Func<Product> func1 = new Func<Product>(productFactory.MakePizza);
            Func<Product> func2 = new Func<Product> (productFactory.MakeToyCar);

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

            Box box1 = wrapFactory.WrapProduct(func1, act1);
            Box box2 = wrapFactory.WrapProduct(func2, act1);

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

        }
    }
    //记录程序的运行状态
    class Logger {
        public void Log(Product product) {
            Console.WriteLine($"Product{product.Name} created at {DateTime.UtcNow}. Price is {product.Price}");
            //UtcNow是不带时区的时间
        }
    }

    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();
            //如果产品的价格大于50就记录一下
            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 = "Toy Car";
            product.Price = 100;
            return product;
        }
    }
}

滥用的示例

using System.Security.Claims;

namespace DelegateExample {

    internal class Program {
        static void Main(string[] args) {
            Operation opt1 = new Operation();
            Operation opt2 = new Operation();
            Operation opt3 = new Operation();

            opt3.InnerOperation = opt2;
            opt2.InnerOperation = opt1;

            opt3.Operate(new object(), null, null);
            /* 问题1:如果传入的两个参数为null,失败和成功的效果是什么?
             * 答:内层的操作会调用外层的回调!
             * 问题2:如果传入的两个参数不为null,会出现什么情况?
             * 答:所有默认的callback都会被“穿透性”屏蔽
             * 
             */
        }
    }
    
    class Operation {
        public Action DefaultSuccessCallback {  get; set; }
        public Action DefaultFailureCallback { get; set;}
        public Operation InnerOperation { get; set; }

        public Object Operate(Object input, Action successCallback, Action failureCallback){
            if(successCallback == null) {
                successCallback = this.DefaultSuccessCallback;
            }
            if(failureCallback == null) {
                failureCallback = this.DefaultFailureCallback;
            }

            Object result = null;
            try {
                result = this.InnerOperation.Operate(input, successCallback, failureCallback);
                // Do something here
            } catch {
                failureCallback.Invoke();
            }

            successCallback.Invoke();
            return result;
        }
    }
}

委托的高级使用

在这里插入图片描述

多播委托

多播委托:即一个委托的内部封装的不止一个方法,封装的多个方法的执行顺序,由方法封装的先后顺序决定

using System.Threading;

namespace MulticastDelegateExample {
    internal 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 act1 = new Action(stu1.DoHomework);
            Action act2 = new Action(stu2.DoHomework);
            Action act3 = new Action(stu3.DoHomework);

            //act1.Invoke();
            //act2.Invoke();
            //act3.Invoke();
            //一个委托封装一个方法的形式,就是单播委托
            //这是同步调用

            //多播委托:
            //将act2的方法也封装到act1中
            act1 += act3;
            act1 += act2;

            act1.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 {this.ID} doing homework {i} hour(s).");
                Thread.Sleep(1000);//让线程暂停一秒
            }
        }
    }
}

同步调用与异步调用

同步调用:

using System.Threading;

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

            //间接同步调用
            Action act1 = new Action(stu1.DoHomework);
            Action act2 = new Action(stu2.DoHomework);
            Action act3 = new Action(stu3.DoHomework);

            act1.Invoke();
            act2.Invoke();
            act3.Invoke();

            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} hour(s).");
                Thread.Sleep(1000);//让线程暂停一秒
            }
        }
    }
}

异步调用

BeginInvoke方法会生成一个分支线程,在这个分支线程中再调用封装的方法

当多个线程访问同一个资源的时候,就有可能在争抢资源的时候发生冲突。

在这里插入图片描述

为了避免冲突,我们会为线程加锁,后面才学的高阶内容。

隐式的异步调用,不知道为什么运行失败了

using System.Threading;

namespace MulticastDelegateExample {
    internal 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 act1 = new Action(stu1.DoHomework);
            Action act2 = new Action(stu2.DoHomework);
            Action act3 = new Action(stu3.DoHomework);
            /* 隐式异步调用
             * 这个方法要传两个参数,
             * 第一个参数是回调方法,也就是当这个线程做完了,还需要做什么
             * 第二个参数是Object,一般就为null
             */

            act1.BeginInvoke(null, null);
            act2.BeginInvoke(null, null);
            act3.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} hour(s).");
                Thread.Sleep(1000);//让线程暂停一秒
            }
        }
    }
}

显式的异步调用:

古老的办法:

using System.Threading;

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

            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} hour(s).");
                Thread.Sleep(1000);//让线程暂停一秒
            }
        }
    }
}

更高级的办法

using System.Threading;

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

            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} hour(s).");
                Thread.Sleep(1000);//让线程暂停一秒
            }
        }
    }
}

使用接口代替委托

使用委托:

using System.Security.Claims;

namespace DelegateExample {

    internal 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; }
        public double Price { 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";
            product.Price = 12;
            return product;
        }
        public Product MakeToyCar() {
            Product product = new Product();
            product.Name = "Toy Car";
            product.Price = 100;
            return product;
        }
    }
}

使用接口:

using System.Security.Claims;

namespace DelegateExample {
    internal 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 Make();
    }

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

    class ToyCarFactory : IProductFactory {
        public Product Make() {
            Product product = new Product();
            product.Name = "Toy Car";
            product.Price = 100;
            return product;
        }
    }

    class Product {
        public string Name { get; set; }
        public double Price { 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;
        }
    }
}

委托的综合示例

下面的示例演示如何声明、实例化和使用委托。

  • BookDB 类封装用来维护书籍数据库的书店数据库。 它公开一个方法 ProcessPaperbackBooks,用于在数据库中查找所有平装书并为每本书调用委托。
  • 使用的 delegate 类型名为 ProcessBookCallback
  • Test 类使用此类打印平装书的书名和平均价格。

使用委托提升书店数据库和客户端代码之间的良好分隔功能。

客户端代码程序不知道如何存储书籍或书店代码如何查找平装书。

书店代码不知道它在找到平装书之后对其执行什么处理。

//包含处理书店数据库的类
namespace Bookstore {
    using System.Collections;

    //书结构体
    public struct Book {
        public string Title;
        public string Author;
        public decimal Price;
        public bool Paperback;//是否是平装书

        public Book(string title, string author, decimal price, bool paperback)
        {
            Title = title;
            Author = author;
            Price = price;
            Paperback = paperback;
        }
    }

    //处理书籍的委托
    public delegate void ProcessBookCallback(Book book);

    //书店数据库类
    public class BookDB {
        //书籍列表
        ArrayList list = new ArrayList();

        //向数据库添加书籍
        public void AddBook(string title, string author, decimal price, bool paperback) {
            list.Add(new Book(title, author, price, paperback));
        }

        //接受一个委托作为参数,并对每本平装书调用该委托
        public void ProcessPaperbackBooks(ProcessBookCallback processBook) {
            foreach (Book book in list) {
                //遍历每一本书,只有平装书才调用委托
                if (book.Paperback)
                {
                    //调用委托
                    processBook(book);
                }
            }
        }
    }
}

namespace BookTestClient {

    using Bookstore;

    //用来计算书的总数和平均价格
    class PriceTotaller {
        int countBooks = 0;
        decimal priceBooks = 0.0m;

        internal void AddBookToTotal(Book book) {
            countBooks++;
            priceBooks += book.Price;
        }

        internal decimal AveragePrice() {
            return priceBooks / countBooks;
        }
    }

    internal class Test {

        static void PrintTitle(Book book) {
            Console.WriteLine($"   {book.Title}");
        }

        static void Main(string[] args) {
            BookDB bookDB = new BookDB();

            //初始化数据库
            AddBooks(bookDB);

            //平装书的书名有:
            Console.WriteLine("Paperback Book Titles:");

            //在数据库类中调用方法
            //并传入一个打印书名的方法,作为委托、
            //打印所有书的书名
            bookDB.ProcessPaperbackBooks(PrintTitle);

            //为了调用实例方法,创建实例
            PriceTotaller totaller = new PriceTotaller();

            //调用数据库中的方法,并传入一个实例方法,作为委托
            //计算书的总数和总价格
            bookDB.ProcessPaperbackBooks(totaller.AddBookToTotal);

            //打印出平装书的平均价格
            Console.WriteLine("Average Paperback Book Price: ${0:#.##}",
                    totaller.AveragePrice());
        }

        //初始化数据库
        static void AddBooks(BookDB bookDB) {
            bookDB.AddBook("The C Programming Language", "Brian W. Kernighan and Dennis M. Ritchie", 19.95m, true);
            bookDB.AddBook("The Unicode Standard 2.0", "The Unicode Consortium", 39.95m, true);
            bookDB.AddBook("The MS-DOS Encyclopedia", "Ray Duncan", 129.95m, false);
            bookDB.AddBook("Dogbert's Clues for the Clueless", "Scott Adams", 12.00m, true);
        }
    }
}

问题总结

当委托构造为封装实例方法时,委托将同时引用实例和方法。 委托不知道除其所封装方法以外的实例类型,因此委托可以引用任何类型的对象,只要该对象上有与委托签名匹配的方法。 当委托构造为封装静态方法时,委托仅引用方法。

这段话应该如何理解?

委托将同时引用实例和方法,是什么意思?

这是查到的解答:

  • 委托类似于C++中的函数指针,但是委托是完全面向对象的,不同于C++指针,它会同时封装对象实例和方法。
  • 当委托引用实例方法时,它不仅存储对方法入口点的引用,还存储对调用该方法的实例的引用
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值