C# 委托
最后修改于 2023 年 7 月 5 日
在本文中,我们将介绍如何在 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 基类。 计数和时间成员是将使用事件初始化和携带的数据。
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# Func 委托
Func 是一种内置的泛型委托类型。Func 可以与方法、匿名方法或 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# Action 委托
action 委托 封装了一个没有参数且不返回值的方法。
Action act = ShowMessage;
act();
void ShowMessage()
{
Console.WriteLine("C# language");
}
使用预定义的委托可以进一步简化编程。 我们不需要声明委托类型。
Action act = ShowMessage; act();
我们实例化一个 action 委托。 该委托指向 ShowMessage 方法。 调用委托时,将执行 ShowMessage 方法。
有多种类型的 action 委托。 例如,Action<T> 委托封装了一个接受单个参数且不返回值的方法。
Action<string> act = ShowMessage;
act("C# language");
void ShowMessage(string message)
{
Console.WriteLine(message);
}
我们修改上一个示例以使用接受一个参数的 action 委托。
Action<string> act = ShowMessage;
act("C# language");
我们创建 Action<T> 委托的一个实例,并使用一个参数调用它。
C# Predicate 委托
谓词是返回 true 或 false 的方法。 谓词委托是对谓词的引用。 谓词对于过滤值列表非常有用。
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;
}
对于所有大于 3 的值,谓词返回 true。
在本文中,我们介绍了如何在 C# 中使用委托。
来源
作者
列出所有 C# 教程。