C#学习笔记 委托

什么是委托

  • 委托(delegate)是函数指针的“升级版”
    • 实例:C/C++中的函数指针
#include <iostream>

//声明函数指针
typedef int (*Calc)(int a, int b);
//Calc 函数指针类型的名字
//指向的函数有两个整数类型的参数;返回整数类型的值

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;
}
  • 一切皆地址
    • 变量(数据)是以某个地址为起点的一段内存中所存储的值
    • 函数(算法)是以某个地址为起点的一段内存中所存储的一组机器语言指令
  • 直接调用与间接调用
    • 直接调用:通过函数名来调用函数,CPU通过函数名直接获得函数所在地址并开始执行→返回
    • 间接调用:通过函数指针来调用函数,CPU通过读取函数指针存储的值获得函数所在地址并开始执行→返回
  • Java中没有与委托相对应的功能实体
  • 委托的简单使用
    • Action委托
    • Func委托
    class Program
    {
        static void Main(string[] args)
        {
            Calculator calculator = new Calculator();
            Action action = new Action(calculator.Report);
            //Action委托指向了Report方法,这里只需要calculator.Report的方法名,不加()
            calculator.Report();    //直接调用
            action.Invoke();        //间接调用
            action();               //模仿函数指针的书写格式

            Func<int, int, int> func1 = new Func<int, int, int>(calculator.Add);
            Func<int, int, int> func2 = new Func<int, int, int>(calculator.Sub);
            //前2个int为参数类型,最后一个为返回值类型
            int x = 100;
            int y = 200;
            int z = 0;
            //z = func1.Invoke(x, y);
            z = func1(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;
        }
    }

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

  • 委托是一种类(class),类是数据类型,所以委托也是一种数据类型
  • 它的声明方式与一般的类不同,主要是为了照顾可读性和C/C++传统
    //要把类声明在名称空间体里,这样它和Program类是平级的
    public delegate double Calc(double x, double y);
    //delegate关键字 告诉编译器要声明委托

    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 = 7;
            double b = 3;
            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 x,double y)
        {
            return x + y;
        }
        public double Sub(double x, double y)
        {
            return x - y;
        }
        public double Mul(double x, double y)
        {
            return x * y;
        }
        public double Div(double x, double y)
        {
            return x / y;
        }
    }
  • 注意声明委托的位置
    • 避免写错地方结果声明成嵌套类型
  • 委托与所封装的方法必需“类型兼容”
    类型兼容
    • 返回值的数据类型一致
    • 参数列表在个数和数据类型上一致(参数名不需要一样)

委托的一般使用

  • 实例:把方法当做参数传给另一个方法
    • 正确使用1:模板方法,“借用”指定的外部方法来产生结果
      • 相当于“填空题”
      • 常位于代码中部
      • 委托有返回值
    • 正确使用2:回调(callback)方法,调用指定的外部方法
      • 相当于“流水线”
      • 常位于代码末尾
      • 委托无返回值

模板方法:

    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)
        //委托类型的参数getProduct封装外部的方法MakePizza,把MakePizza方法传进WrapProduct方法的内部,再进行间接调用
        {
            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)方法:
指通过委托类型的参数传进主调方法的一个被调用方法,主调方法可以根据自己的逻辑决定是否调用这个方法。

    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);

            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)
        {
            Console.WriteLine("Product'{0}' created at {1}.Price is {2}.",product.Name,DateTime.UtcNow,product.Price);
            //DateTime.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();
            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 = 30;
            return product;
        }
        public Product MakeToyCar()
        {
            Product product = new Product();
            product.Name = "ToyCar";
            product.Price = 100;
            return product;
        }
    }
  • 注意:难精通+易使用+功能强大的东西,一旦被滥用则后果非常严重
    • 缺点1:这是一种方法级别的紧耦合,现实工作中要慎之又慎
    • 缺点2:使可读性下降,debug的难度增加
    • 缺点3:把委托回调、异步调用和多线程纠缠在一起,会让代码变得难以阅读和维护
    • 缺点4:委托使用不当有可能造成内存泄漏和程序性能下降

委托的高级使用

  • 多播(multicast)委托
    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;
            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} hour(s).",this.ID,i);
                Thread.Sleep(1000);
                //指你在哪个线程当中调用这个Thread.Sleep,那么这个线程就睡1s(1000ms)
            }
        }
    }
  • 隐式异步调用
    • 同步与异步的简介
      • 中英文的语言差异
      • 同步:你做完了(在你的基础上)接着做
      • 异步:咱们俩同时做(相对于汉语中的“同步进行”)
    • 同步调用与异步调用的对比
      • 每一个运行的程序是一个进程(process)
      • 每个进程可以有一个或多个线程(thread)
      • 同步调用是在同一个线程内
      • 异步调用的底层机理是多线程
      • 串行= =同步= =单线程,并行= =异步= =多线程
    • 隐式多线程 v.s. 显式多线程
      • 直接同步调用:使用方法名
      • 间接同步调用:使用单播/多播委托的Invoke方法
      • 隐式异步调用:使用委托的BeginInvoke
      • 显示异步调用:使用Thread或Task

直接同步调用:

    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 {0}.",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{0} doing homework {1} hour(s).",this.ID,i);
                Thread.Sleep(1000);
            }
        }
    }

间接同步调用:

    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);
			//间接同步调用:使用单播/多播委托的Invoke方法
			//单播委托:
            action1.Invoke();
            action2.Invoke();
            action3.Invoke();
            /*多播委托:
            action1 += action2;
            action1 += action3;
            action1.Invoke();*/

            for (int i = 0; i < 10; i++)
            {
                Console.ForegroundColor = ConsoleColor.Cyan;
                Console.WriteLine("Main thread {0}.",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{0} doing homework {1} hour(s).",this.ID,i);
                Thread.Sleep(1000);
            }
        }
    }

隐式异步调用:

    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);
            //BeginInvoke进行间接调用,就是隐式的异步调用。
            //它会自动生成一个分支线程,然后在分支线程里调用它封装的方法。
            //第一个参数:异步调用的回调,在分支线程中调用方法完之后,后续采取的行动
            //当多个线程访问同一个资源的时候,就有可能在争抢这个资源的时候发生冲突

            for (int i = 0; i < 10; i++)
            {
                Console.ForegroundColor = ConsoleColor.Cyan;
                Console.WriteLine("Main thread {0}.",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{0} doing homework {1} hour(s).",this.ID,i);
                Thread.Sleep(1000);
            }
        }
    }

显式异步调用:
1.使用Thread进行显式异步调用:

    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进行显示异步调用
            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 {0}.",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{0} doing homework {1} hour(s).",this.ID,i);
                Thread.Sleep(1000);
            }
        }
    }

2.使用Task进行显式异步调用:

    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进行显式异步调用
            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 {0}.",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{0} doing homework {1} hour(s).",this.ID,i);
                Thread.Sleep(1000);
            }
        }
    }
  • 应该适时地使用接口(interface)取代一些对委托的使用
    • Java完全地使用接口取代了委托的功能,即Java没有与C#中委托相对应的功能实体
    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";
            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;
        }
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值