在本文中,我们使用C#中的委托。
委托定义
委托是一种类型安全的方法指针。它引用具有特定参数列表和返回类型的方法。
委托是一种引用类型。但是委托不是指一个对象,而是指一个方法。我们可以通过委托实例调用可以引用的方法。
委托用于以下情况:
- 事件处理程序
- 回调
- 将方法作为方法参数传递
- LINQ
- 设计模式的实现
没有什么是常规方法不能用委托完成的。使用委托是因为它们带来了几个优点。它们促进了应用程序的灵活性和代码重用。当我们需要决定在运行时调用哪个方法时,我们使用委托。
C#使用委托
我们有一些简单的例子展示了如何使用委托。
var md = new MyDelegate(MyCallback); md(); void MyCallback() { Console.WriteLine("Calling callback"); } delegate void MyDelegate();
我们声明一个委托,创建委托的实例并调用它。
var md = new MyDelegate(MyCallback);
我们创建委托实例。调用时,委托将调用Callback
方法。
md();
我们调用委托。
delegate void MyDelegate();
这是我们的委托声明。它不返回任何值,也不接受任何参数。
$ dotnet run Calling callback
我们可以使用不同的语法来创建和使用委托。
MyDelegate del = MyCallback; del(); void MyCallback() { Console.WriteLine("Calling callback"); } delegate void MyDelegate();
我们可以在创建委托实例时节省一些输入。
MyDelegate del = MyCallback;
这是创建委托的另一种方式。我们直接指向方法名。
C#委托指向不同的方法
随着时间的推移,委托可以指向不同的方法。
var per = new Person("Fabius", "Maximus"); var nDelegate = new NameDelegate(per.ShowFirstName); nDelegate("Call 1:"); nDelegate = new NameDelegate(per.ShowSecondName); nDelegate("Call 2:"); public delegate void NameDelegate(string msg); public class Person { public string firstName; public string secondName; public Person(string firstName, string secondName) { this.firstName = firstName; this.secondName = secondName; } public void ShowFirstName(string msg) { Console.WriteLine($"{msg} {this.firstName}"); } public void ShowSecondName(string msg) { Console.WriteLine($"{msg} {this.secondName}"); } }
在示例中,我们有一个代表。该委托用于指向Person
类的两个方法。这些方法由委托调用。
var nDelegate = new NameDelegate(per.ShowFirstName); nDelegate("Call 1:");
我们创建一个指向ShowFirstName
方法的新委托实例。稍后我们通过委托调用该方法。
public delegate void NameDelegate(string msg);
委托是使用delegate
关键字创建的。委托签名必须与委托调用的方法的签名相匹配。
$ dotnet run Call 1: Fabius Call 2: Maximus
两个名字都是通过委托打印的。
C#多播委托
多播委托是一种持有对多个方法的引用的委托。多播委托必须仅包含返回void的方法,否则会出现运行时异常。
var del = new MyDelegate(Oper.Add); del += new MyDelegate(Oper.Sub); del(6, 4); del -= new MyDelegate(Oper.Sub); del(2, 8); delegate void MyDelegate(int x, int y); public class Oper { public static void Add(int x, int y) { Console.WriteLine("{0} + {1} = {2}", x, y, x + y); } public static void Sub(int x, int y) { Console.WriteLine("{0} - {1} = {2}", x, y, x - y); } }
这是多播委托的示例。
var del = new MyDelegate(Oper.Add);
我们创建了一个委托实例。委托指向Oper
类的静态Add
方法。
del += new MyDelegate(Oper.Sub); del(6, 4);
我们将另一个方法插入到现有的委托实例中。委托的第一次调用会调用两个方法。
del -= new MyDelegate(Oper.Sub); del(2, 8);
我们从委托中删除了一个方法。委托的第二次调用仅调用一个方法。
delegate void MyDelegate(int x, int y);
我们的委托有两个参数。我们有一个Oper
类,它有两个静态方法。一个将两个值相加,另一个将两个值相减。
$ dotnet run 6 + 4 = 10 6 - 4 = 2 2 + 8 = 10
C#匿名方法
委托可以使用匿名方法。
MyDelegate del = delegate { Console.WriteLine("Anonymous method"); }; del(); delegate void MyDelegate();
当使用带有委托的匿名方法时,我们可以省略方法声明。该方法没有名称,只能通过委托调用。
MyDelegate del = delegate { Console.WriteLine("Anonymous method"); };
这里我们创建了一个指向匿名方法的委托。匿名方法的主体由{}
字符包围,但没有名称。
C#委托作为方法参数
委托可以用作方法参数。
DoOperation(10, 2, Multiply); DoOperation(10, 2, Divide); void DoOperation(int x, int y, Arithm del) { int z = del(x, y); Console.WriteLine(z); } int Multiply(int x, int y) { return x * y; } int Divide(int x, int y) { return x / y; } delegate int Arithm(int x, int y);
我们有一个DoOperation
方法,它接受一个委托作为参数。
DoOperation(10, 2, Multiply); DoOperation(10, 2, Divide);
我们调用DoOperation
方法。我们向它传递两个值和一个方法。我们如何处理这两个值取决于我们传递的方法。这就是使用委托带来的灵活性。
void DoOperation(int x, int y, Arithm del) { int z = del(x, y); Console.WriteLine(z); }
这是DoOperation
方法的实现。第三个参数是委托。DoOperation
方法调用作为第三个参数传递给它的方法。
delegate int Arithm(int x, int y);
这是一个委托声明。
$ dotnet run 20 5
C#事件
事件是由某些操作触发的消息。单击按钮或时钟的滴答声就是这样的动作。触发事件的对象称为发送者,接收事件的对象称为接收者。
按照惯例,.NET中的事件委托有两个参数:引发事件的源和事件的数据。
var fe = new FEvent(); fe.FiveEvent += new OnFiveHandler(Callback); var random = new Random(); for (int i = 0; i < 10; i++) { int rn = random.Next(6); Console.WriteLine(rn); if (rn == 5) { fe.OnFiveEvent(); } } void Callback(object sender, EventArgs e) { Console.WriteLine("Five Event occurred"); } class FEvent { public event OnFiveHandler FiveEvent; public void OnFiveEvent() { if (FiveEvent != null) { FiveEvent(this, EventArgs.Empty); } } } public delegate void OnFiveHandler(object sender, EventArgs e);
我们有一个创建和启动事件的简单示例。生成一个随机数。如果数字等于5,则生成一个FiveEvent
事件。
fe.FiveEvent += new OnFiveHandler(Callback);
在这里,我们将名为FiveEvent
的事件插入到Callback
方法中。换句话说,如果触发了ValueFive
事件,则执行Callback
方法。
public event OnFiveHandler FiveEvent;
事件是用event
关键字声明的。
public void OnFiveEvent() { if(FiveEvent != null) { FiveEvent(this, EventArgs.Empty); } }
当随机数等于5时,我们调用OnFiveEvent
方法。在此方法中,我们引发了FiveEvent
事件。此事件不带任何参数。
$ dotnet run 1 1 5 Five Event occurred 1 1 4 1 2 4 5 Five Event occurred
C#复杂事件示例
接下来我们有一个更复杂的例子。这次我们发送一些数据和生成的事件。
namespace ComplexEvent; public delegate void OnFiveHandler(object sender, FiveEventArgs e); public class FiveEventArgs : EventArgs { public int count; public DateTime time; public FiveEventArgs(int count, DateTime time) { this.count = count; this.time = time; } } public class FEvent { public event OnFiveHandler FiveEvent; public void OnFiveEvent(FiveEventArgs e) { FiveEvent(this, e); } } public class RandomEventGenerator { public void Generate() { int count = 0; FiveEventArgs args; var fe = new FEvent(); fe.FiveEvent += new OnFiveHandler(Callback); var random = new Random(); for (int i = 0; i < 10; i++) { int rn = random.Next(6); Console.WriteLine(rn); if (rn == 5) { count++; args = new FiveEventArgs(count, DateTime.Now); fe.OnFiveEvent(args); } } } public void Callback(object sender, FiveEventArgs e) { Console.WriteLine("Five event {0} occurred at {1}", e.count, e.time); } } class Program { static void Main() { var reg = new RandomEventGenerator(); reg.Generate(); } }
我们有四个班级。FiveEventArgs
携带事件对象的一些数据。FEvent
类封装了事件对象。RandomEventGenerator
类负责随机数的生成,是事件的发送者。最后,ComplexEvent
是主应用程序类。
public class FiveEventArgs : EventArgs { public int count; public DateTime time; ...
FiveEventArgs
在事件对象中携带数据。它继承自EventArgs
基类。count和time成员是将被初始化并随事件携带的数据。
if (rn == 5) { count++; args = new FiveEventArgs(count, DateTime.Now); fe.OnFiveEvent(args); }
如果生成的随机数等于5,我们将使用当前计数和DateTime
值实例化FiveEventArgs
类。count
变量计算此事件生成的次数。DateTime
值保存事件生成的时间。
$ dotnet run 2 1 0 5 Five event 1 occurred at 1/7/2022 1:16:03 PM 1 3 1 1 0 3
C#预定义委托
.NET有几个内置的委托,可以减少所需的输入并使开发人员的编程更容易。
C#函数委托
函数
是内置的通用委托类型。
函数
可以与方法、匿名方法或lambda表达式一起使用。
Func
可以包含0到16个输入参数,并且必须有一个返回类型。(Func
委托有16个重载。)
public delegate TResult Func<in T, out TResult>(T arg);
例如,此委托封装了一个方法,该方法具有一个参数并返回由TResult
参数指定的类型的值。
string GetMessage() { return "Hello there!"; } Func<string> sayHello = GetMessage; Console.WriteLine(sayHello());
在示例中,我们使用Func
委托,它没有参数并返回单个值。
$ dotnet run Hello there!
C#动作委托
actiondelegate封装了一个没有参数且不返回值的方法。
Action act = ShowMessage; act(); void ShowMessage() { Console.WriteLine("C# language"); }
使用预定义的委托进一步简化了编程。我们不需要声明委托类型。
Action act = ShowMessage; act();
我们实例化一个动作委托。委托指向ShowMessage
方法。调用委托时,将执行ShowMessage
方法。
有多种类型的动作委托。例如,Action
委托封装了一个采用单个参数且不返回值的方法。
Action<string> act = ShowMessage; act("C# language"); void ShowMessage(string message) { Console.WriteLine(message); }
我们修改前面的示例以使用带有一个参数的动作委托。
Action<string> act = ShowMessage; act("C# language");
我们创建了一个Action委托实例并使用一个参数调用它。
C#谓词委托
谓词是一种返回真或假的方法。谓词委托是对谓词的引用。谓词对于过滤值列表非常有用。
List<int> vals = new List<int> { 4, 2, 3, 0, 6, 7, 1, 9 }; Predicate<int> myPred = greaterThanThree; List<int> vals2 = vals.FindAll(myPred); foreach (int i in vals2) { Console.WriteLine(i); } bool greaterThanThree(int x) { return x > 3; }
我们有一个整数值列表。我们要过滤所有大于三的数字。为此,我们使用谓词委托。
List<int> vals = new List<int> { 4, 2, 3, 0, 6, 7, 1, 9 };
这是整数值的通用列表。
Predicate<int> myPred = greaterThanThree;
我们创建谓词委托的实例。委托指向谓词,一种返回true或false的特殊方法。
List<int> vals2 = vals.FindAll(myPred);
FindAll
方法检索与指定谓词定义的条件匹配的所有元素。
bool greaterThanThree(int x) { return x > 3; }
谓词对所有大于三的值返回真。
在本文中,我们使用了C#中的委托。
列出所有C#教程。