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






4.79/5 (20投票s)
本系列文章“深入面向对象编程”将介绍 C# 中关于委托的所有内容。本文更侧重于实际实现,而较少关注理论。文章深入讲解了该概念。
目录
简介
本系列文章“深入面向对象编程”将介绍 C# 中关于委托的所有内容。本文更侧重于实际实现,而较少关注理论。文章深入讲解了该概念。
委托 (定义)
让我们从 MSDN 上的定义开始:
"委托声明定义了一种引用类型,该类型可用于封装具有特定签名的方法。委托实例封装静态方法或实例方法。委托大致类似于 C++ 中的函数指针;但是,委托是类型安全且安全的。"
本系列其他文章
以下是 OOP 系列所有其他文章的列表。
- 深入 OOP(第一天):多态与继承(早期绑定/编译时多态)
- 深入OOP(第2天):多态性和继承(继承)
- 深入OOP(第3天):多态性和继承(动态绑定/运行时多态)
- 深入 OOP (第四天):多态与继承 (关于 C# 中的抽象类)
- 深入OOP(第5天):C#中访问修饰符的一切(Public/Private/Protected/Internal/Sealed/Constants/Readonly字段)
- 学习 C#(第 6 天):理解 C# 中的枚举(实用方法)
- 学习 C# (第 7 天):C# 中的属性 (实用方法)
- 学习 C# (第 8 天):C# 中的索引器 (实用方法)
- 学习 C#(第 9 天):理解 C# 中的事件(深入探讨)
- 学习C#(第10天):C#中的委托(一种实用方法)
- 学习C#(第11天):C#中的事件(一种实用方法)
委托(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 类中,delegateExercises
是 DelegateExercises
类的实例,并且调用了 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();
}
}
}
输出
现在这是一个稍微复杂但值得理解的场景。实例 ex1Delegate
、ex2Delegate
、ex3Delegate
、myDelegate
是委托类型的对象。实例 ex1Delegate
表示 Method1
,ex2Delegate
表示 Method2
。因此,ex1Delegate("AAA")
调用 Method1
,ex2Delegate("BBB")
调用 Method2
。实例 ex3Delegate
显示了 ex1Delegate
和 ex2Delegate
的相加。这里的相加不是指将两个委托像数字一样相加,而是指应调用并执行两个委托。首先调用 Method1
,然后调用 Method2
,并且在输出中也很清楚。现在实例 myDelegate
被初始化为 ex1Delegate – ex2Delegate
,这再次不是数学减法,而是此实现将 Method2
中包含的方法从 Method1
中移除。由于我们在 method2
中没有公共方法,因此它没有意义,并且调用了 Method1
。
实例 myDelegate
再次等于 ex3Delegate- ex1Delegate
。现在这将从对象 ex3Delegate
中消除委托 ex1Delegate
所代表的所有方法。如前所述,实例 ex3Delegate
代表方法 Method1
和 Method2
,现在由于 ex1Delegate
代表方法 Method1
,因此 Method1
从 myDelegate
中被消除。所以调用了 Method2
。
在另一种情况 ex3Delegate – ex2Delegate
中,如果实例 ex3Delegate
被执行,Method1
和 Method2
都将被调用。但由于我们正在减去 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”参数在多播委托中是允许的。
结论
本文详细介绍了委托。委托非常关键,需要理解但实现起来很棘手。希望本帖能帮助读者对委托有所了解。