C # 委托

1.什么是委托

       委托是函数指针的“升级版”,委托是一种类,类是数据类型,所以委托也是一种数据类型,委托与类都属于引用类型。

指向多个方法的委托叫做多播委托。

                        C # 委托_第1张图片

2.直接调用与间接调用

     函数的两种调用方式:直接调用/间接调用     显而易见的,通过委托调用函数属于间接调用。

3.自定义委托

  //委托的声明
  public delegate void MyDelegate();// ←(这里声明的委托参数列表为空)//注意! 该委托声明在名称空间体内 而不是在类体里
   //        ↑       ↑
  //delegate关键字  返回值类型(这里声明的委托返回值类型为空)
  //该委托就可以指向Log方法,因为Log方法的返回值类型为空,且参数列表为空! 委托是需要 类型兼容的! 类型兼容! 类型兼容!

  public class MyClass
  {
      //类的声明
      public void Log()
      {
          //方法的声明
          Console.WriteLine("简单例子");
      }
  }

       注意声明委托的位置!委托的声明一般如同类的声明,是在名称空间体内的,如果你将委托声明在了类体里,那委托就会声明成一个嵌套类型。

       委托的声明方式与一般的类不同,主要是为了照顾可读性和C/C++传统

       我们可以根据实际情况自定义委托的返回值类型与参数列表,注意委托所指向的方法必须要与委托类型兼容(返回值类型与参数列表)。

  static void Main(string[] args)
  {
      MyClass myClass = new MyClass();
      MyDelegate myDelegate = new MyDelegate(myClass.Log);//myDelegate委托指向myClass中的Log方法
  }

        注意!为什么我们在new MyDelegate(myClass.Log)的Log后没有加()呢?因为在这里我们并不是去调用myClass.Log这个方法,所以不需要加上该方法的构造器,在这里我们只是需要myClass.Log这个方法的方法名!

4.C#为我们准备好的两种委托Action和Func

       Action委托 无返回值类型(一定无),16种重载,最多支持16个参数。

static void Main(string[] args)
{
    Calculator calculator = new Calculator();
    Action action = new Action(calculator.Report);            
    calculator.Report();//直接调用
    action.Invoke();//间接调用
    action();

       上图中的Action委托 无参数列表,指向Calculator类中的Report方法(同样无返回值,无参数列表,类型兼容!)如果你想使用带有参数的Action委托,在Action后+<>(如Action<>)。

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

        Func委托,有返回值类型(一定有),17种重载,最多支持16个参数(可以无参数)。

 Func func1 = new Func(calculator.Add);
 Func func2 = new Func(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);
 Console.ReadLine();

      上图中的func1和func2委托,均含有两个int类型的参数,并且返回值类型也为int。

       他们所指向的Add与Sub方法与fun1和func2类型兼容!

C # 委托_第2张图片

5.回调方法和模板方法

C # 委托_第3张图片        模板方法就如下面代码中的WrapProduct(Func getProduct)一样,借用了作为参数传进来的委托所指向的方法,这是委托的一个用法。

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

        Func func1 = new Func(productFactory.MakePizza);//func1委托指向productFactory.MakePizza方法
        Func func2 = new Func(productFactory.MakeToyCar);//func2委托指向productFactory.MakeToyCar方法
        //func1 委托与func2委托的返回值类型均为Product 指向的方法返回值类型也均为Product 类型兼容!


        Box box1 = wrapFactory.WrapProduct(func1);//此时相当于将productFactory.MakePizza方法当作一个参数传给了(利用委托)wrapFactory.WrapProduct方法 所以称为模板方法
        Box box2 = wrapFactory.WrapProduct(func2);//此时相当于将productFactory.MakeToyCar方法当作一个参数传给了(利用委托)wrapFactory.WrapProduct方法 所以称为模板方法

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

   
}


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

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

class WrapFactory
{
    public Box WrapProduct (Func getProduct)//参数为一个委托类型参数  
    {
        Box box=new Box();
        Product product = getProduct.Invoke();
        box.Product= product;
        return box;
    }
}
class ProductFactory//ProductFactory类中的两个Make方法的返回值类型都为Product
{
    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;
    }
   
   
}

       模板方法与回调方法的最大区别就在于,模板方法,“我”一定使用,而回调方法,“我可以不用,但不能没有”,回调方法是可以被选择是否使用的(根据具体情况),并且模板方法(用Func)有返回值,回调方法(用Action)无返回值,下面代码中的例子就是产品的价格大于等于50,才选择执行回调方法的逻辑。

 internal class Program
 {
  
     static void Main(string[] args)
     {
         ProductFactory productFactory = new ProductFactory();
         WrapFactory wrapFactory = new WrapFactory();
         Logger logger = new Logger();
         Action log = new Action(logger.Log);
         //Action委托log 与logger.Log方法类型兼容

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


         Box box1 = wrapFactory.WrapProduct(func1,log);//此时相当于又将log方法当作一个参数传给了(利用委托)wrapFactory.WrapProduct方法 
         Box box2 = wrapFactory.WrapProduct(func2,log);//此时相当于又将log方法当作一个参数传给了(利用委托)wrapFactory.WrapProduct方法 

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

    
 }

 
   class Logger
 {
     public void Log(Product product)
     {
         Console.WriteLine("Product'{0}'created at{1}.Price is{2}",product.Name,DateTime.UtcNow,product.Price);
     }
 }
 class Product
 { 
     public string Name{ get; set; }
     public int Price { get; set; }
 }

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

 class WrapFactory
 {
     public Box WrapProduct (Func getProduct,Action logCallback)//将Action委托指向的方法当作参数传进了WrapProduct方法
     {
         Box box=new Box();
         Product product = getProduct.Invoke();
         if (product.Price >= 50)//回调方法与模板方法的最大区别,“可以不用,但不能没有”,即我不一定执行你的方法
         {
             logCallback(product);
         }

         box.Product= product;
         return box;
     }
 }
 class ProductFactory//ProductFactory类中的两个Make方法的返回值类型都为Product
 {
     public Product MakePizza()
     {
         Product product= new Product();
         product.Name = "Pizza";
         product.Price = 10;
         return product;
     }

     public Product MakeToyCar()
     {
         Product product = new Product();
         product.Name = "Toy Car";
         product.Price= 50;
         return product;
     }     
 }

6.单播委托与多播委托

       单播委托指的是一个委托只指向了一个方法,如下面的代码所示,action1,action2,action3三个委托都分别指向了stu1,stu2,stu3的一个DoHomework方法,它们均为单播委托

  static void Main(string[] args)
  {
      Student stu1=new Student() { ID=1,PenColor=ConsoleColor.Red};
      Student stu2 = new Student() { ID = 2, PenColor = ConsoleColor.Blue };
      Student stu3 = new Student() { ID = 3, PenColor = ConsoleColor.Yellow };
      Action action1 = new Action(stu1.DoHomework);
      Action action2 = new Action(stu2.DoHomework);
      Action action3 = new Action(stu3.DoHomework);
      action1();//action1.Inovke() 间接调用
      action2();
      action3();
  }

       我们来看一下Student类,和上面的代码的执行效果。为我们对多播委托的理解作一个铺垫。

 public 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} hour(s)",this.ID,i);
             Thread.Sleep(1000);
         }
     }
 }

C # 委托_第4张图片执行结果

        下图为多播委托,我们可以看到action1委托指向了3个方法(封装了3个方法)。

        一个委托封装多个方法可以通过+=操作符完成,注意如果此时漏掉了+号,只写一个=号的话,会导致一个委托的重置(这也为我们后面引出事件作了铺垫,因为委托的使用是不安全的,就像字段的使用是不安全的,我们会更倾向于使用属性)!

       该多播委托的执行结果与之前的单播委托的执行结果完全相同,我们可以得出,多播委托所封装方法的执行顺序,与封装他们时的封装顺序一致。

static void Main(string[] args)
{
    Student stu1=new Student() { ID=1,PenColor=ConsoleColor.Red};
    Student stu2 = new Student() { ID = 2, PenColor = ConsoleColor.Blue };
    Student stu3 = new Student() { ID = 3, PenColor = ConsoleColor.Yellow };
    Action action1 = new Action(stu1.DoHomework);
    action1 += stu2.DoHomework;
    action1+= stu3.DoHomework;
    action1();
    Console.ReadLine();
}

7.异步调用/同步调用(可以选择性浏览)

C # 委托_第5张图片

       这里我们要注意中英文语言的差异性,区分C#中的同步与异步与我们平时生活用语的不同。

       同步调用有:1.直接同步调用(这里的直接 指的是直接通过方法名调用方法)2.间接同步调用 3.多播委托(这也是间接调用的一种) 

        下图中红色的线代表主线程,蓝色, 绿色,黄色代表stu1,stu2,stu3的Dohomework方法,我们程序的执行是线性的,从左到右依次执行,所以说是同步的(按顺序 依次 执行)

       下面是直接同步调用

static void Main(string[] args)
{
    Student stu1 = new Student() { ID = 1, PenColor = ConsoleColor.Red };
    Student stu2 = new Student() { ID = 2, PenColor = ConsoleColor.Blue };
    Student stu3 = new Student() { ID = 3, PenColor = ConsoleColor.Yellow };

    stu1.DoHomework();//通过方法名直接调用 
    stu2.DoHomework();
    stu3.DoHomework();
    for(int i=0;i<5;i++)
    {
        Console.ForegroundColor = ConsoleColor.Magenta;
        Console.WriteLine("Main thread do something{0}", i);
        Thread.Sleep(1000);
    }
    Console.ReadLine();
}

     下面是间接同步调用 

 static void Main(string[] args)
 {
     Student stu1 = new Student() { ID = 1, PenColor = ConsoleColor.Red };
     Student stu2 = new Student() { ID = 2, PenColor = ConsoleColor.Blue };
     Student stu3 = new Student() { ID = 3, PenColor = ConsoleColor.Yellow };

     Action action1 = new Action(stu1.DoHomework);
     Action action2 = new Action(stu2.DoHomework);
     Action action3 = new Action(stu3.DoHomework);

     action1();//action1.Inovke()   间接调用
     action2();
     action3();
     for (int i=0;i<5;i++)
     {
         Console.ForegroundColor = ConsoleColor.Magenta;
         Console.WriteLine("Main thread do something{0}", i);
         Thread.Sleep(1000);
     }
     Console.ReadLine();
 }

      多播委托(间接调用)

 static void Main(string[] args)
 {
     Student stu1 = new Student() { ID = 1, PenColor = ConsoleColor.Red };
     Student stu2 = new Student() { ID = 2, PenColor = ConsoleColor.Blue };
     Student stu3 = new Student() { ID = 3, PenColor = ConsoleColor.Yellow };

     Action action1 = new Action(stu1.DoHomework);
     action1 += stu2.DoHomework;
     action1 += stu3.DoHomework;
     action1();    //多播委托
     for (int i=0;i<5;i++)
     {
         Console.ForegroundColor = ConsoleColor.Magenta;
         Console.WriteLine("Main thread do something{0}", i);
         Thread.Sleep(1000);
     }
     Console.ReadLine();
 }

      以上三种同步调用的运行结果都是一致的,程序按顺序依次执行,执行结果如下图所示

C # 委托_第6张图片

        下面我们来看看异步调用,我们可以这样理解异步,当我们在干一件事的同时也在干另一件事(好比小时候我们一边写作业一边看电视,这两件事是我们在同时做的)。

C # 委托_第7张图片

        我们通过BeginInvoke方法(参数列表两个参数一般填null值)进行隐式的异步调用, 下面的代码就是三个委托与主线程同时执行,我们来看看执行结果就明白了。

 static void Main(string[] args)
 {
     Student stu1 = new Student() { ID = 1, PenColor = ConsoleColor.Red };
     Student stu2 = new Student() { ID = 2, PenColor = ConsoleColor.Blue };
     Student stu3 = new Student() { ID = 3, PenColor = ConsoleColor.Yellow };

     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.Magenta;
         Console.WriteLine("Main thread do something{0}", i);
         Thread.Sleep(1000);
     }
     Console.ReadLine();
 }

       下面的执行结果我们可以看出,这三个委托与主线程确实是同时执行的,但是为什么打印的颜色出现了问题呢? 这涉及到了我们操作系统的一些知识,由于我们的三个分支线程与主线程都在同时执行,且都去访问了打印颜色这个资源,导致了资源的争抢且发生了冲突,为了避免产生资源争抢而产生冲突,我们通常会为线程加锁(操作系统知识,这里不在过多讨论)

C # 委托_第8张图片

       下面是显式的异步调用,它与隐式的异步调用一样,同样会发生资源的争抢。

static void Main(string[] args)
{
    Student stu1 = new Student() { ID = 1, PenColor = ConsoleColor.Red };
    Student stu2 = new Student() { ID = 2, PenColor = ConsoleColor.Blue };
    Student stu3 = new Student() { ID = 3, PenColor = ConsoleColor.Yellow };

    Thread thread1 = new Thread(new ThreadStart(stu1.DoHomework));//显式异步调用
    Thread thread2 = new Thread(new ThreadStart(stu1.DoHomework));
    Thread thread3 = new Thread(new ThreadStart(stu1.DoHomework));
  
    for (int i=0;i<5;i++)
    {
        Console.ForegroundColor = ConsoleColor.Magenta;
        Console.WriteLine("Main thread do something{0}", i);
        Thread.Sleep(1000);
    }
    Console.ReadLine();
}

     这是我对B站Up主TimothyLiu(刘铁猛老师!!!)与BeaverJoe老师对于委托的学习总结,如有错漏,恳请指出,谢谢!

你可能感兴趣的:(c#,开发语言)