C#委托详解

什么是委托

Action 泛型委托(Delegate)用来封装一个方法且方法不具有参数与返回值

Func 泛型委托(Delegate),用来**表示一个有返回值的方法,**尖括号最后一个类型是返回值,前面的是参数

cs 复制代码
 class Program
 {
     static void Main(string[] args)
     {
         Calculator calculator = new Calculator();

         Action action = new Action(calculator.Report); 
         
         calculator.Report(); //直接调用
         action(); //间接调用
         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);

         int x = 100;
         int y = 200;
         int z = 0;

         z = func1(x, y); //同z = func1.Invoke(x, y);
         Console.WriteLine(z);

         z = func2(x, y);
         Console.WriteLine(z);
     }
 }

 class Calculator 
 {
     public void Report() 
     {
         Console.WriteLine("I hava 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;
     }
 }
   

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

一般的类声明:

cs 复制代码
class 类名
{
    类体
}

委托的声明:放在 namespace 下是最标准、最清晰的写法,放在 类 下则变成嵌套类

cs 复制代码
public delegate double Calc(double x, double y);

public-公开访问

delegate-关键字告诉编译器我要声明委托了

double-目标方法的返回值类型

Calc-委托名

()-目标方法的参数列表

加了括号 (),代表"立刻执行这个方法并拿到结果";不加括号,代表"把这个方法本身当作一个对象传递出去"。 Calc calc1 = new Calc(calculator.Add);与Calc calc1 = new Calc(calculator.Add**()**);

使用委托的方法:

①声明委托;

②创建委托的实例,并将目标方法名称放入委托构造函数的参数列表里;

③执行委托实例的方法,例如Action.Invoke();或者直接用简写Action(),则可以通过委托实例的方法来间接调用目标方法;

cs 复制代码
namespace ConsoleHelloWorld
{
    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;
            double b = 200;
            double 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 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;

②好处简介:可以将可能产生变化的业务逻辑部分放在方法1中,而方法2当作一个"模板"(即模板方法) ,通过固定的委托间接调用方法1,这样当业务逻辑需求发生变化时,就不用该方法2的代码了,而只需要改方法1的代码,甚至直接将委托中封装的方法换一个以实现其它功能;(填空题)

cs 复制代码
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();           // 1. 先拿个空箱子
        Product product = getProduct();// 2. 调用委托,拿到产品!唯一变的,填空题的空
        box.Product = product;         // 3. 把产品装进箱子里
        return box;                    // 4. 把装好的箱子交出去
    }
}

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

回调方法

可以调用也可以不调用,用得着的时候就调用,用不着就不调用,可以动态的选择后续将被调用的方法。如:A给B一张名片,告诉B有需要可以打电话给A,同时又有C、D等也给B名片,B可以动态的选择后续需要谁的帮忙。

把委托类型的参数传到主调方法里,被传进主调方法的委托类型的参数内部会封装被回调的方法,主调函数根据自己的逻辑来确认要不要调用回调方法。回调方法常位于主调函数末尾,无返回值。

cs 复制代码
namespace ConsoleHelloWorld
{
    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) 
        {
            //不带时区的UtcNow
            Console.WriteLine("Product '{0}' created at {1}.Price is {2}.",product.Name,DateTime.UtcNow,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();           // 1. 先拿个空箱子
            Product product = getProduct();// 2. 调用委托,拿到产品!唯一变的,填空题的空
            if (product.Price >= 50) 
            {
                logCallback(product);
            }
            box.Product = product;         // 3. 把产品装进箱子里
            return box;                    // 4. 把装好的箱子交出去
        }
    }

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

委托的高级使用

单播委托

1个委托封装1个方法。

cs 复制代码
using System;
using System.Threading;

namespace ConsoleHelloWorld
{
    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(); //action.Invoke();
            action2();
            action3();
        }
    }

    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);  //线程休眠1000ms(1s)
            }
        }
    }

}

多播委托

1个委托中封装着不止1个方法

多播委托在调用的时候,它执行的顺序是按照封装方法的先后顺序来执行的

cs 复制代码
using System;
using System.Threading;

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

    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);  //线程休眠1000ms(1s)
            }
        }
    }

}

第一个运行起来的线程就是进程的主线程,其他线程叫做分支线程。

同步调用(单线程调用)

在同一个线程内调用方法时,方法的执行时前一个执行完,后一个才执行,在同一个线程内依次执行的方法调用叫做同步调用

直接同步调用:
cs 复制代码
using System;
using System.Threading;

namespace ConsoleHelloWorld
{
    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);  //线程休眠1000ms(1s)
            }
        }
    }

}
单播委托的间接同步调用:
cs 复制代码
using System;
using System.Threading;

namespace ConsoleHelloWorld
{
    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 < 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);  //线程休眠1000ms(1s)
            }
        }
    }

}

多播委托的间接同步调用:

异步调用(多线程调用)

在不同的线程中去调用方法,每个线程跟另外一个线程都互不相干(指的是逻辑上互不相干,现实工作中经常会遇到多个线程共享(同时访问)同一个资源(比如某个变量)的情况, 这时候如果处理不当就会产生线程间争夺资源的冲突),一个线程的开始和结束并不会影响另一个线程的开始和结束。

隐式异步调用

begininvoke自动生成分支线程去调用封装的方法

cs 复制代码
using System;
using System.Threading;

namespace ConsoleHelloWorld
{
    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 {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);  //线程休眠1000ms(1s)
            }
        }
    }

}

主线程与分支线程谁也不等谁,同时执行:

显式异步调用

自己动手生成多线程

cs 复制代码
using System;
using System.Threading;

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

            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);  //线程休眠1000ms(1s)
            }
        }
    }

}
cs 复制代码
using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleHelloWorld
{
    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 {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);  //线程休眠1000ms(1s)
            }
        }
    }

}

*用类声明一个类类型的属性/字段,和用类创建一个类实例的区别是什么?

本质上是一样的,用类创建一个实例,赋值左边的就是先声明了一个类类型的变量,其实也可以看做是一个字段(字段就是一种变量);

相关推荐
czq_26867194871 小时前
Python打卡第45天
开发语言·python
ZoeLandia1 小时前
React04:事件处理与受控表单
开发语言·前端·react.js·typescript
熊猫钓鱼>_>1 小时前
端侧高斯泼溅(3DGS)渲染与性能调优初探:把“实景重建“跑在手机上【鸿蒙心迹】
开发语言·3d·华为·智能手机·harmonyos
量化炼金 (CodeAlchemy)1 小时前
【Python】pandas vs polars 简单性能对比测试
开发语言·python·pandas
苦瓜汤补钙1 小时前
Matlab 2022b(中文)的使用教程——Win11
开发语言·matlab
Ai-_Man1 小时前
我需要批量导出Poe左边栏所有对话,而不是一条具体的对话内容,你们AI导出鸭可以吗?
开发语言·前端·javascript·人工智能·小程序
福大大架构师每日一题1 小时前
rust 1.99.1 正式更新:聚焦 UEFI、嵌入式、x86_64 FFI 与 macOS 链接体验的三项修复
开发语言·macos·rust
Pin_Qing1 小时前
缓存能活多久,现在是个要算的参数
java·大数据·开发语言·人工智能·程序人生
小道士写程序1 小时前
Rust 元组详解:复合类型的基础
开发语言·rust