《C# 语言入门详解(刘铁锰) - 学习笔记 - 委托》

委托

文章目录

  • 委托
  • 前言
  • 一、委托是什么?
  • 二、委托的一般使用
  • 三、委托的高级使用


前言

  • 委托: 函数指针的“升级版”;(C/C++中的函数指针)
  • 委托分类: Action委托、Function委托、自定义委托;
  • ”一切皆地址“: 程序(本质) = 数据 + 算法;(变量(数据) 是以某个地址为起点的一段内存中所存储的值;函数(算法) 是以某个地址为起点的一段内存中所存储的一组机器语言指令)
  • Java 中没有与委托相对应的功能实体

一、委托是什么?

  • 定义: 引用类型(在委托对象的引用中,存放的是对方法的引用,而不是对数据的引用),是一种类;

1. 值类型和引用类型的相同点:

  • 引用类型可以实现接口,值类型当中的结构体也可以实现接口;
  • 引用类型和值类型都继承自System.Object类。

2. 值类型和引用类型的区别:

  • 1)范围方面
    C#的值类型包括:结构体(数值类型、bool型、用户定义的结构体),枚举,可空类型。
    C#的引用类型包括:数组,用户定义的类、接口、委托,object,字符串。
  • 2)内存分配方面:
    数组的元素不管是引用类型还是值类型,都存储在托管堆上。
    引用类型在栈中存储一个引用,其实际的存储位置位于托管堆。简称引类型部署在托管推上;
    而值类型总是分配在它声明的地方:作为字段时,跟随其所属的变量(实 例)存储;作为局部变量时,存储在栈上。(栈的内存是自动释放的,堆内存是.NET中会由GC来自动释放)
  • 3)适用场合
    值类型在内存管理方面具有更好的效率,并且不支持多态,适合用做存储数据的载体;引用类型支持多态,适合用于定义应用程序的行为。
    引用类型可以派生出新的类型,而值类型不能,因为所有的值类型都是密封(seal)的;
    引用类型可以包含null值,值类型不能(可空类型功能允许将 null 赋给值类型,如 int? a = null; );
    引用类型变量的赋值只复制对对象的引用,而不复制对象本身。而将一个值类型变量赋给另一个值类型变量时,将复制包含的值。
  • Action 委托: 指向无返回值成员;
using System;

namespace InterfaceTest
{
    internal class Program
    {
        public static void Main(string[] args)
        {
            Calculator calculator = new Calculator();
            Action action = new Action(calculator.Report);//Action委托

            calculator.Report();//直接调用
            action.Invoke();//间接调用
            action();//间接调用
        }
    }
    class Calculator
    {
        public void Report()
        {
            System.Console.WriteLine("I have 3 methods");
        }

        public int Sum(int a, int b)
        {
            return a + b;
        }
    }
}
  • Function委托: 指向有返回值成员(Func<参数1类型,参数2类型,返回值类型>);
using System;

namespace InterfaceTest
{
    internal class Program
    {
        public static void Main(string[] args)
        {
            Calculator calculator = new Calculator();
            Func<int, int, int> func = new Func<int, int, int>(calculator.Sum);//func<>委托

            calculator.Sum(2, 3);//直接调用
            func.Invoke(2, 3);//间接调用
            func(2, 3); //间接调用
        }
    }
    class Calculator
    {
        public void Report()
        {
            System.Console.WriteLine("I have 3 methods");
        }

        public int Sum(int a, int b)
        {
            return a + b;
        }
    }
}
  • 自定义委托: 【修饰符】 delegate 【返回类型】【名称】(【参数列表】) (例如:public delegate int Add(int x,int y))
using System;

namespace InterfaceTest
{
    public delegate double Cal(double x, double y);//委托声明
    class Program
    {
        static void Main(string[] args)
        {
            Calculator calculator = new Calculator();
            Cal cal = new Cal(calculator.Sum);//委托实例化,方式一
            Cal cal1 = calculator.Sub;//委托实例化,方式二

            System.Console.WriteLine(cal(3, 2));
            System.Console.WriteLine(cal1(3, 2));
        }
    }
    class Calculator
    {
        public void Report()
        {
            System.Console.WriteLine("I have 3 methods");
        }
        public double Sum(double a, double b)
        {
            return a + b;
        }
        public double Sub(double a, double b)
        {
            return a - b;
        }
    }
}

二、委托的一般使用

  • 模板方法: 借用指定的外部方法产生结果(可最大限度重用代码
using System;

namespace InterfaceTest
{
    public delegate double Cal(double x, double y);//委托声明
    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.MakeToCar);

            Box box1 = wrapFactory.WrapProduct(func1); //调用产品封装方法,输出含产品名字信息的box类
            Box box2 = wrapFactory.WrapProduct(func2);

            System.Console.WriteLine(box1.Product.Name); //输出结果
            System.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 MakeToCar()
        {
            Product product = new Product();
            product.Name = "MakeToCar";
            return product;
        }
    }
}
  • 回调(callback)方法: 调用指定的外部方法
using System;

namespace InterfaceTest
{
    public delegate double Cal(double x, double y);//委托声明
    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.MakeToCar);

            Logger logger = new Logger(); //声明产品记录方法
            Action<Product> log = new Action<Product>(logger.Log);

            Box box1 = wrapFactory.WrapProduct(func1, log); //调用产品封装方法,输出含产品名字信息的box类
            Box box2 = wrapFactory.WrapProduct(func2, log);

            System.Console.WriteLine(box1.Product.Name); //输出结果
            System.Console.WriteLine(box2.Product.Name);
        }
    }

    //定义记录产品信息类
    class Logger
    {
        public void Log(Product product)
        {
            System.Console.WriteLine("Product'{0}' create at {1} Price is {2}.", product.Name, DateTime.UtcNow, 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();//调用委托方法,即所传入委托参数的成员类型
            if (product.Price > 50)
            {
                logCallBack.Invoke(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 MakeToCar()
        {
            Product product = new Product();
            product.Name = "MakeToCar";
            product.Price = 100;
            return product;
        }
    }
}
  • 模板方法和回调方法的本质: 用委托类型的参数,封装一个外部方法,然后将此方法传递到方法内部,进行间接调用。

1. 模板方法与回调方法的区别:

  • 模板方法:
    1)相当于“填空题”;
    2)常位于代码中部;
    3)委托有返回值
  • 回调方法:
    1)相当于“流水线”;
    2)常位于代码末尾;
    3)委托无返回值

2. 委托不足(使用需注意,避免滥用,后果严重)

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

三、委托的高级使用

  • 单播委托:一个委托封装一个方法;
using System;
using System.Threading;

namespace InterfaceTest
{
    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();
        }
    }

    class Student
    {
        public int ID { get; set; }
        public ConsoleColor PenColor { get; set; }
        public void DoHomework()
        {
            for (int i = 0; i < 3; i++)
            {
                Console.ForegroundColor = this.PenColor;
                Console.WriteLine("Student {0} doing homework {1} hours", this.ID, i);
                Thread.Sleep(1000);
            }
        }
    }
}

【输出结果】
《C# 语言入门详解(刘铁锰) - 学习笔记 - 委托》_第1张图片

  • 多播(Multicast)委托:一个委托封装多个方法,调用执行顺序为封装方法的先后顺序
using System;
using System.Threading;

namespace InterfaceTest
{
    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 < 3; i++)
            {
                Console.ForegroundColor = this.PenColor;
                Console.WriteLine("Student {0} doing homework {1} hours", this.ID, i);
                Thread.Sleep(1000);
            }
        }
    }
}

【输出结果】- 同上

  • 同步调用(直接 / 间接)
  • 直接同步调用:
using System;
using System.Threading;

namespace InterfaceTest
{
    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 < 5; i++)
            {
                Console.ForegroundColor = ConsoleColor.Cyan;
                Console.WriteLine("Main hread {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 < 3; i++)
            {
                Console.ForegroundColor = this.PenColor;
                Console.WriteLine("Student {0} doing homework {1} hours", this.ID, i);
                Thread.Sleep(1000);
            }
        }
    }
}

【输出结果】
《C# 语言入门详解(刘铁锰) - 学习笔记 - 委托》_第2张图片

  • 间接同步调用:
using System;
using System.Threading;

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

            for (int i = 0; i < 5; i++)
            {
                Console.ForegroundColor = ConsoleColor.Cyan;
                Console.WriteLine("Main hread {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 < 3; i++)
            {
                Console.ForegroundColor = this.PenColor;
                Console.WriteLine("Student {0} doing homework {1} hours", this.ID, i);
                Thread.Sleep(1000);
            }
        }
    }
}

【输出结果】- 同上

  • 异步调用(隐式 / 显示)
  • 隐式异步调用(委托)-BeginInvoke(.Net Core 框架不适用,.Net Framework框架可以)
using System;
using System.Threading;

namespace InterfaceTest
{
    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 < 5; 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 < 3; i++)
            {
                Console.ForegroundColor = this.PenColor;
                Console.WriteLine("Student {0} doing homework {1} hours", this.ID, i);
                Thread.Sleep(1000);
            }
        }
    }
}

【输出结果】
《C# 语言入门详解(刘铁锰) - 学习笔记 - 委托》_第3张图片

  • 显示异步调用(Thread / Task)
using System;
using System.Threading;

namespace InterfaceTest
{
    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(stu1.DoHomework);  //显示异步调用Thread
            Thread thread2 = new Thread(stu2.DoHomework);
            Thread thread3 = new Thread(stu3.DoHomework);

            thread1.Start();
            thread2.Start();
            thread3.Start();

            for (int i = 0; i < 5; 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 < 3; i++)
            {
                Console.ForegroundColor = this.PenColor;
                Console.WriteLine("Student {0} doing homework {1} hours", this.ID, i);
                Thread.Sleep(1000);
            }
        }
    }
}
using System;
using System.Threading;
using System.Threading.Tasks;

namespace InterfaceTest
{
    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(stu1.DoHomework);  //显示异步调用Task
            Task Task2 = new Task(stu2.DoHomework);
            Task Task3 = new Task(stu3.DoHomework);

            Task1.Start();
            Task2.Start();
            Task3.Start();

            for (int i = 0; i < 5; i++)
            {
                Console.ForegroundColor = ConsoleColor.Cyan;
                Console.WriteLine("Main Task {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 < 3; i++)
            {
                Console.ForegroundColor = this.PenColor;
                Console.WriteLine("Student {0} doing homework {1} hours", this.ID, i);
                Thread.Sleep(1000);
            }
        }
    }
}

【输出结果】
《C# 语言入门详解(刘铁锰) - 学习笔记 - 委托》_第4张图片

  • 注意: 应该适时的使用接口(interface)取代一些对委托的使用。(Java完全地使用接口取代了委托的功能,即Java没有C#中委托相对应的功能实体)
using System;

namespace InterfaceTest
{
    public delegate double Cal(double x, double y);//委托声明
    class Program
    {
        static void Main(string[] args)
        {
            IProductFactory PizzaFactory = new PizzaFactory(); //接口子类实例化,多态
            IProductFactory TocarFactory = new TocarFactory();
            WrapFactory wrapFactory = new WrapFactory();  //声明产品封装方法

            Box box1 = wrapFactory.WrapProduct(PizzaFactory); 
            Box box2 = wrapFactory.WrapProduct(TocarFactory);

            System.Console.WriteLine(box1.Product.Name); //输出结果
            System.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 TocarFactory : IProductFactory
    {
        public Product Make()
        {
            Product product = new Product();
            product.Name = "MakeToCar";
            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;
        }
    }  
}

1.同步与异步的简介

  • 中英文语言理解的差异
  • 同步:你做完了我接着做(在你的基础上,有顺序的)
  • 异步:咱两同时做(同步进行,无顺序)

2.同步调用和异步调用的对比

  • 每一个运行的程序都是一个进程(process);
  • 每个进程可以有一个或多个线程(thread);
  • 同步调用是在同一线程内;异步调用时多线程的;
  • 串行=>同步=>单线程;并行=>异步=>多线程

3.同步调用 and 异步调用

  • 直接同步调用:使用方法名;
  • 间接同步调用:使用单播/多播委托的Invoke方法;
  • 隐式异步调用:使用委托的BeginInvoke;
  • 显示异步调用:使用Thread 或 Task;

你可能感兴趣的:(学习笔记,c#)