65.9K
CodeProject 正在变化。 阅读更多。
Home

学习C#(第10天):C#中的委托(一种实用方法)

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.79/5 (20投票s)

2016 年 11 月 7 日

CPOL

9分钟阅读

viewsIcon

46752

downloadIcon

952

本系列文章“深入面向对象编程”将介绍 C# 中关于委托的所有内容。本文更侧重于实际实现,而较少关注理论。文章深入讲解了该概念。

目录

简介

本系列文章“深入面向对象编程”将介绍 C# 中关于委托的所有内容。本文更侧重于实际实现,而较少关注理论。文章深入讲解了该概念。

委托 (定义)

让我们从 MSDN 上的定义开始:

"委托声明定义了一种引用类型,该类型可用于封装具有特定签名的方法。委托实例封装静态方法或实例方法。委托大致类似于 C++ 中的函数指针;但是,委托是类型安全且安全的。"

本系列其他文章

以下是 OOP 系列所有其他文章的列表。

委托(Delegates)

委托是 C# 编程语言中最有趣的功能之一。它可以直接放置在命名空间中,就像类一样。委托完全遵循面向对象编程的规则。委托扩展了 System.Delegate 类。

委托最适合匿名调用,因为它们可以调用方法签名中提供的任何方法,即返回类型和参数相同。让我们尝试通过实际示例来涵盖该主题。

实验 1

创建一个控制台应用程序,并随意命名,我将其命名为 EventsAndDelegates。添加一个名为 DelegateExercises 的公共类,并实现如下。

DelegateExercises

using System;
 
namespace DelegatesAndEvents
{
    public class DelegateExercises
    {
        public delegate void MyDelegate();
        void Method1()
        {
            Console.WriteLine("Method1");
            Console.ReadLine();
        }
        public void Method2()
        {
            MyDelegate myDelegate = new MyDelegate(Method1);
            myDelegate();
        }
 
    }
}

Program.cs 类调用 Method2 方法。

程序

namespace DelegatesAndEvents
{
    class Program
    {
        static void Main(string[] args)
        {
            DelegateExercises delegateExercises=new DelegateExercises();
            delegateExercises.Method2();
        }
    }
}

输出

在 Program 类中,delegateExercisesDelegateExercises 类的实例,并且调用了 delegateExercises.Method2() 方法。在创建实例时,我们遵循使用 new 关键字创建实例的规则。同样,我们也可以在委托名称中使用 new,如 delegateExercises 类的 Method2 中所示。委托非常类似于 C# 中的属性或索引器,即它是类的一等成员。它看起来像一个函数,但使用名为 delegate 的关键字定义。在上面的 Method2 示例中,我们创建了委托的实例,并将整个函数,即 Method1 作为参数传递。这意味着方法本身也可以使用委托作为参数传递。这就是 C# 通常处理回调方法或事件处理程序的方式。要实例化委托,传统的“new”关键字用于一个参数,即方法本身“Method1”。Method1 是“DelegateExercises”类的一个成员,返回类型为 void,不接受任何参数。new 关键字(一如既往)会创建一个类型为 delegate 的对象,以及一个方法。

"Method1" 以非传统方式调用,即不使用 Method1() 语法。因为在创建对象时将方法作为参数传递,所以 "myDelegate" 是方法 "Method1" 的委托对象。因此,当调用 "myDelegate" 时,意味着调用了 "Method1",从而提供了抽象级别。

实验 2

DelegateExercises

using System;
 
namespace DelegatesAndEvents
{
    public class DelegateExercises
    {
        public delegate void MyDelegate();
        void Method1()
        {
            Console.WriteLine("Method1");
            Console.ReadLine();
        }
        public void Method2()
        {
            MyDelegate myDelegate = new MyDelegate(Method1);
            myDelegate(50);
        }
 
    }
}

Program.cs 类调用 Method2 方法。

程序

namespace DelegatesAndEvents
{
    class Program
    {
        static void Main(string[] args)
        {
            DelegateExercises delegateExercises=new DelegateExercises();
            delegateExercises.Method2();
        }
    }
}

输出

当我们尝试带参数调用委托时,会收到错误:“Delegate 'DelegateExercises.MyDelegate' does not take 1 arguments”(委托 'DelegateExercises.MyDelegate' 不接受 1 个参数)。我们将 50 作为参数传递给 Method1 方法,但您可以清楚地看到 Method1 不接受整数参数,因此在编译时会显示错误。

实验 3

DelegateExercises

using System;
 
namespace DelegatesAndEvents
{
    public class DelegateExercises
    {
        public delegate void MyDelegate();
        void Method1(int i)
        {
            Console.WriteLine("Method1");
            Console.ReadLine();
        }
        public void Method2()
        {
            MyDelegate myDelegate = new MyDelegate(Method1);
            myDelegate();
        }
 
    }
}

程序

namespace DelegatesAndEvents
{
    class Program
    {
        static void Main(string[] args)
        {
            DelegateExercises delegateExercises=new DelegateExercises();
            delegateExercises.Method2();
        }
    }
}

输出

我们再次收到错误消息,但这次不同。我们假设添加一个整数参数将解决先前的错误,但此错误表明委托签名应与方法的签名匹配。

实验 4

using System;
 
namespace DelegatesAndEvents
{
 
  public class DelegateExercises
  {
    public delegate int MyDelegate(int intValue);
 
    public int Method1(int intMethod1)
    {
      return intMethod1*2;
    }
 
    public int Method2(int intMethod2)
    {
      return intMethod2*10;
    }
 
    public void Method3()
    {
      MyDelegate myDelegate = new MyDelegate(Method1);
      int result1 = myDelegate(10);
      System.Console.WriteLine(result1);
      myDelegate = new MyDelegate(Method2);
      int result2 = myDelegate(10);
      System.Console.WriteLine(result2);
    }
  }
 
  public class Program
  {
    public static void Main()
    {
      DelegateExercises delegateExercises = new DelegateExercises();
      delegateExercises.Method3();
      Console.ReadLine();
 
    }
  }
}

输出

为摆脱错误,向委托 MyDelegate 添加了一个整数参数。这意味着调用委托的方法将自动处理该参数。因此,此委托现在返回一个整数而不是 void。最后,当通过委托运行方法时,编译器也会检查返回参数。在 Method3 中,我们现在使实现更加棘手,并创建另一种委托类型,其中另一个方法名称作为参数,我们再次使用相同的参数(值为整数:10)执行委托,但要调用的方法每次都会改变,因此代码可以更动态地编写。

实验 5

namespace DelegatesAndEvents
{
  public class Program
  {
    public static void Main()
    {
      DelegateExercises delegateExercises = new DelegateExercises();
      delegateExercises.Method3();
    }
  }
 
  public class DelegateExercises
  {
    public delegate int MyDelegate();
 
    void Method1()
    {
      System.Console.WriteLine("MyDelegate");
    }
 
    public void Method3()
    {
      MyDelegate myDelegate = new MyDelegate(Method1);
      myDelegate();
    }
  }
}

输出

错误:“void DelegateExercises.Method1()”的返回类型不正确

在此,编译器发现了一个不匹配。如果委托在上述代码提到的上下文中用于,其返回类型理想情况下应为整数,但 Method1 返回 void。

实验 6

using System;
 
namespace DelegatesAndEvents
{
  public class Program
  {
    public static void Main()
    {
      DelegateExercises delegateExercises = new DelegateExercises();
      delegateExercises.Method3();
      Console.ReadLine();
    }
  }
 
  public class DelegateExercises
  {
    public delegate int MyDelegate(int intValue);
 
    int Method1(int intMethod1)
    {
      return intMethod1*2;
    }
 
    int Method2(int intMethod1)
    {
      return intMethod1*10;
    }
 
    public void Method4(MyDelegate myDelegate)
    {
      int result = myDelegate(10);
      Console.WriteLine(result);
    }
 
    public void Method3()
    {
      MyDelegate myDelegate = new MyDelegate(Method1);
      Method4(myDelegate);
      myDelegate = new MyDelegate(Method2);
      Method4(myDelegate);
    }
  }
}

输出

上面编写的代码只是试图解释委托只不过是一个类。委托对象可以作为参数传递,如 Method4 中所示。

要点:由于委托是一种数据类型,因此可以将其传递给方法。

在方法 Method4 的第一次和第二次调用中,将类型为 MyDelegate 的相同委托传递了过去。第一次调用 MyDelegate 方法,第二次调用 Method2。使用对象 myDelegate,每次都会执行不同的方法。间接地,每次都将不同的方法作为参数传递给 Method4。这就像编写通用且抽象的代码,其中 Method4 实际上并不关心它是如何被调用的,以及传递给它的参数是什么。它将执行与实现分离。

实验 7

using System;
 
namespace DelegatesAndEvents
{
  public class Program
  {
    public static void Main()
    {
      DelegateExercises delegateExercises = new DelegateExercises();
      delegateExercises.Method3();
      Console.ReadLine();
    }
  }
 
  public class DelegateExercises
  {
    public delegate int MyDelegate(int intValue);
 
    int Method1(int intMethod1)
    {
      return intMethod1*4;
    }
 
    int Method2(int intMethod1)
    {
      return intMethod1*20;
    }
 
    public void Method4(MyDelegate myDelegate)
    {
      for (int i = 1; i <= 5; i++)
        System.Console.Write(myDelegate(i) + " ");
    }
 
    public void Method3()
    {
      MyDelegate myDelegate = new MyDelegate(Method1);
      Method4(myDelegate);
      myDelegate = new MyDelegate(Method2);
      Method4(myDelegate);
    }
  }
}

输出

在上面的实现中,实现了更多的分离和抽象。此实现说明:“我们反复强调的是,委托也可以在循环结构内执行。”在上面的代码中,委托被作为参数传递给一个方法,并在运行时执行该方法的名称,而无需在编译时知道其详细信息。

实验 8

using System;
 
namespace DelegatesAndEvents
{
  public class Program
  {
    public static void Main()
    {
      DelegateExercises delegateExercises = new DelegateExercises();
      delegateExercises.Method3();
      Console.ReadLine();
    }
 
  }
  public delegate void MyDelegate();

  public class DelegateExercises
  {
    void Method1()
    {
      System.Console.WriteLine("Method1");
    }
    public void Method3()
    {
      MyDelegate myDelegate = new MyDelegate(Method1);
      myDelegate();
    }
  }
 
}

输出

输出是 Method1,因为 MyDelegate 作为委托定义了一个扩展 System.Delegate 的类。

实验 9

using System;
 
namespace DelegatesAndEvents
{
  public delegate void MyDelegate();
  public class DelegateExercises : MyDelegate
  {
  }
}

输出

错误:“DelegateExercises”:无法从已密封的类型“MyDelegate”派生

委托在内部以同名类表示。在上面的代码中,类 MyDelegate 是隐式密封的,另一个类不能从密封类派生。

要点:System.Delegate 是一个抽象类,所有委托都自动从此类派生。

实验 10

using System;
 
namespace DelegatesAndEvents
{
  public class Program
  {
    public static void Main()
    {
      DelegateExercises delegateExercises = new DelegateExercises();
      delegateExercises.Method3();
      Console.ReadLine();
    }
  }
 
  public delegate void MyDelegate();
 
  public class DelegateExercises
  {
    void Method1()
    {
      System.Console.WriteLine("Method1");
 
 
    }
 
    public void Method3()
    {
      MyDelegate myDelegate = new MyDelegate(Method1);
      myDelegate();
      System.Console.WriteLine(myDelegate.ToString());
    }
  }
 
 
}

输出

这仅表明 .ToString() 方法也可以对委托调用,因为 System.Delegate 也派生自最终基类,即 Object。

实验 11

public delegate void MyDelegate();
 
 public class DelegateExercises
 {
   void Method3()
   {
     System.Console.WriteLine(MyDelegate.ToString());
   }
 }

输出

错误:非静态字段、方法或属性 'object.ToString()' 需要对象引用

委托 MyDelegate 的名称不能与 .ToString() 方法一起使用,因为它不是静态的,而作为类对象的 myDelegate 可以毫无错误地使用。

实验 12

using System;
 
namespace DelegatesAndEvents
{
  using System;
 
  delegate void ExampleDelegate(string xyz);
 
  class Program
  {
    public static void Method1(string xyz)
    {
      Console.WriteLine(xyz + " Method1");
    }
 
    public static void Method2(string xyz)
    {
      Console.WriteLine(xyz + " Method2");
    }
 
    public static void Main()
    {
      ExampleDelegate ex1Delegate, ex2Delegate, ex3Delegate, myDelegate;
 
      ex1Delegate = new ExampleDelegate(Method1);
      ex2Delegate = new ExampleDelegate(Method2);
      ex3Delegate = ex1Delegate + ex2Delegate;
      myDelegate = ex1Delegate - ex2Delegate;
      ex1Delegate("AAA");
      ex2Delegate("BBB");
      ex3Delegate("CCC");
      myDelegate("DDD");
      myDelegate = ex3Delegate - ex1Delegate;
      myDelegate("EEE");
      myDelegate = ex3Delegate - ex2Delegate;
      myDelegate("FFF");
      Console.ReadLine();
    }
  }
}

输出

现在这是一个稍微复杂但值得理解的场景。实例 ex1Delegateex2Delegateex3DelegatemyDelegate 是委托类型的对象。实例 ex1Delegate 表示 Method1ex2Delegate 表示 Method2。因此,ex1Delegate("AAA") 调用 Method1ex2Delegate("BBB") 调用 Method2。实例 ex3Delegate 显示了 ex1Delegateex2Delegate 的相加。这里的相加不是指将两个委托像数字一样相加,而是指应调用并执行两个委托。首先调用 Method1,然后调用 Method2,并且在输出中也很清楚。现在实例 myDelegate 被初始化为 ex1Delegate – ex2Delegate,这再次不是数学减法,而是此实现将 Method2 中包含的方法从 Method1 中移除。由于我们在 method2 中没有公共方法,因此它没有意义,并且调用了 Method1

实例 myDelegate 再次等于 ex3Delegate- ex1Delegate。现在这将从对象 ex3Delegate 中消除委托 ex1Delegate 所代表的所有方法。如前所述,实例 ex3Delegate 代表方法 Method1Method2,现在由于 ex1Delegate 代表方法 Method1,因此 Method1myDelegate 中被消除。所以调用了 Method2

在另一种情况 ex3Delegate – ex2Delegate 中,如果实例 ex3Delegate 被执行,Method1Method2 都将被调用。但由于我们正在减去 ex2Delegate,因此只调用了 Method1

要点:可以通过委托调用任意数量的方法。

实验 13

using System;
 
namespace DelegatesAndEvents
{
  public class Program
  {
    public static void Main()
    {
      DelegateExercises delegateExercises = new DelegateExercises();
      delegateExercises.Method3();
      Console.ReadLine();
    }
  }
 
  public delegate int MyDelegate(out int i);
 
  public class DelegateExercises
  {
    int Method1(out int i)
    {
      System.Console.WriteLine("Method1");
      i = 10;
      return 0;
    }
 
    public void Method3()
    {
      MyDelegate myDelegate = new MyDelegate(Method1);
      MyDelegate myDelegate1 = new MyDelegate(Method1);
      MyDelegate myDelegate2 = myDelegate + myDelegate1;
      int intValue;
      myDelegate2(out intValue);
 
    }
  }
 
}

输出

在上面的示例中,我们看到委托方法也可以接受 out 参数,因此我们也得到了结果。

实验 14

using System;
 
namespace DelegatesAndEvents
{
  public class Program
  {
    public static void Main()
    {
      DelegateExercises delegateExercises = new DelegateExercises();
      delegateExercises.Method3();
      Console.ReadLine();
    }
  }
  public delegate int MyDelegate(out int i);

  public class DelegateExercises
  {
    int Method1(out int i)
    {
      i = 100;
      System.Console.WriteLine("Method1 " + i);
      return 0;
    }
    public void Method3()
    {
      MyDelegate myDelegate = new MyDelegate(Method1);
      MyDelegate myDelegate1 = null;
      MyDelegate myDelegate2 = myDelegate + myDelegate1;
      int intValue;
      myDelegate2(out intValue);
    }
  }
 
 }

输出

相同的代码现在可以工作了。唯一的区别是其中一个委托被赋值为 null。这些是简单委托的规则,但多播委托的规则更为严格。

两个独立的委托可以指向同一个函数和目标对象。因此,“+”运算符帮助我们添加委托,“-”运算符帮助我们从一个委托中删除另一个委托。

实验 15

using System;
 
namespace DelegatesAndEvents
{
  public class Program
  {
    public static void Main()
    {
      DelegateExercises delegateExercises = new DelegateExercises();
      try
      {
        delegateExercises.Method3();
        Console.ReadLine();
      }
      catch (System.Exception ex)
      {
        System.Console.WriteLine("Exception Occurred.");
        Console.ReadLine();
 
      }
    }
  }
 
  public delegate void MyDelegate();
 
  public class DelegateExercises
  {
    void Method1()
    {
      throw new System.Exception();
    }
 
    public void Method3()
    {
      MyDelegate myDelegate = new MyDelegate(Method1);
      myDelegate();
    }
  }
 
}

输出

在上面的代码中,myDelegate() 调用 Method1 方法。

该方法抛出异常。我们在 Method3 中选择不处理此异常,而是在调用 Method3 的 Main 方法中处理。我们看到委托引起的异常会一直传播,直到被捕获。

实验 16

using System;
 
namespace DelegatesAndEvents
{
  public class Program
  {
    public static void Main()
    {
      DelegateExercises delegateExercises = new DelegateExercises();
      delegateExercises.Method3();
      Console.ReadLine();
 
    }
  }
 
  public delegate void MyDelegate(ref int intValue);
 
  public class DelegateExercises
  {
    void Method1(ref int intValue)
    {
      intValue = intValue + 5;
      System.Console.WriteLine("Method1 " + intValue);
    }
 
    public void Method3()
    {
      MyDelegate myDelegate = new MyDelegate(Method1);
      MyDelegate myDelegate1 = new MyDelegate(Method1);
      MyDelegate myDelegate2 = myDelegate + myDelegate1;
      int intParameter = 5;
      myDelegate2(ref intParameter);
    }
  }
 
}

输出

在前面的示例中,我们看到了“out”参数,并得出结论,它们未在多播委托中使用。而在上面的示例中,我们看到传递“ref”参数在多播委托中是允许的。

结论

本文详细介绍了委托。委托非常关键,需要理解但实现起来很棘手。希望本帖能帮助读者对委托有所了解。

© . All rights reserved.