C# 委托事件

一、C# 委托
委托是一种表示对具有特定参数列表和返回类型的方法的引用的类型。实例化委托时,可以将其实例与具有兼容签名和返回类型的任何方法相关联。您可以通过委托实例调用(或调用)该方法。

    委托用于将方法作为参数传递给其他方法。事件处理程序只不过是通过委托调用的方法。您创建一个自定义方法,当某个事件发生时,诸如 windows 控件之类的类可以调用您的方法。以下示例显示了委托声明:

public delegate int PerformCalculation(int x, int y);
委托是 .NET Framework 使用的一种类型安全的函数指针。委托通常用于实现回调和事件侦听器。委托不需要知道任何关于它使用的方法类的信息。

    委托是一种引用类型。但是,委托不是引用对象,而是引用方法。

    委托用于以下情况:
  • Event handlers:事件处理函数

  • Callbacks:回调函数

  • LINQ

  • Implementation of design patterns

     没有什么是委托不能用常规方法完成的。使用代表是因为它们带来了几个优点。它们促进了应用程序和代码重用的灵活性。像接口一样,委托让我们解耦和泛化我们的代码。
    
     委托还允许将方法作为参数传递。当我们需要决定在运行时调用哪个方法时,我们使用委托。最后,委托提供了一种在不继承类的情况下专门化类行为的方法。类可能具有复杂的泛型行为,但仍然是专门的。类通过继承或委托来专门化。
    

二、C#使用委托
2.1 我们将有一些简单的例子来展示如何使用委托。
1)声明一个委托

delegate void MyDelegate();

2)声明一个被委托的函数

void MyCallback()
{
    Console.WriteLine("Calling callback");
}

3)使用委托

Program.cs

var md = new MyDelegate(MyCallback);
md();

This is our delegate declaration. It returns no value and takes no parameters.

$ dotnet run
Calling callback

2.2 我们可以使用不同的语法来创建和使用委托。
Program.cs

MyDelegate del = MyCallback;
del();
void MyCallback()
{
    Console.WriteLine("Calling callback");
}
delegate void MyDelegate();

我们可以在创建委托实例时节省一些输入。

MyDelegate del = MyCallback;
这是另一种创建委托的方式。我们直接指向方法名。

三、C#使用委托指向不同的方法
随着时间的推移,委托可以指向不同的方法。

Program.cs

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

method–稍后我们通过委托调用该方法。

public delegate void NameDelegate(string msg);

委托是使用委托关键字创建的。委托签名必须与被委托调用的方法的签名相匹配。

$ dotnet run
Call 1: Fabius
Call 2: Maximus

这两个名称都是通过委托打印的。

四、C# 多播委托
多播委托是持有对多个方法的引用的委托。多播委托必须只包含返回 void 的方法,否则会出现运行时异常。

Program.cs

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


我们创建一个委托的实例。委托指向 Opera 类的静态 Add 方法。

del += new MyDelegate(Oper.Sub);
del(6, 4);

我们将另一个方法插入现有的委托实例。委托的第一次调用调用两个方法。

del -= new MyDelegate(Oper.Sub);
del(2, 8);

我们从委托中删除一种方法。委托的第二次调用仅调用一种方法。

delegate void MyDelegate(int x, int y);

我们的委托有两个参数。我们有一个 Opera 类,它有两个静态方法。一个加两个值,另一个减两个值。

$ dotnet run
6 + 4 = 10
6 - 4 = 2
2 + 8 = 10w

五、C# 匿名方法

可以对委托使用匿名方法。

Program.cs

MyDelegate del = delegate
{
    Console.WriteLine("Anonymous method");
};

del();

delegate void MyDelegate();
        当使用带有委托的匿名方法时,我们可以省略方法声明。该方法没有名称,只能通过委托调用。

MyDelegate del = delegate
{
    Console.WriteLine("Anonymous method");
};
    在这里,我们创建一个指向匿名方法的委托。匿名方法的主体由 {} 字符括起来,但没有名称。

六、C# 将委托看成是参数
委托可以用作方法参数。

Program.cs

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);
This is a delegate declaration.

$ dotnet run
20
5

七、C# 事件
事件是由某些操作触发的消息。点击按钮或时钟滴答就是这样的动作。触发事件的对象称为发送者,接收事件的对象称为接收者。

    按照惯例,.NET Framework 中的事件委托有两个参数:引发事件的源和事件的数据。

Program.cs

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 的事件插入到回调方法中。换句话说,如果触发了 ValueFive 事件,则会执行 Callback 方法。

public event OnFiveHandler FiveEvent;
An event is declared with a event keyword.

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# 复杂事件示例
接下来我们有一个更复杂的例子。这次我们用生成的事件发送一些数据。

Program.cs

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

我们有四个class。 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 类。计数变量计算此事件生成的次数。 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 框架有几个内置的委托,可以减少所需的输入并使开发人员的编程更容易。

9.1 C# 函数委托
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!

9.2 C# 行动委托
动作委托封装了一个没有参数且不返回值的方法。

Program.cs

Action act = ShowMessage;
act();

void ShowMessage()
{
    Console.WriteLine("C# language");
}
        //使用预定义的委托进一步简化了编程。我们不需要声明委托类型。

Action act = ShowMessage;
act();

我们实例化一个动作委托。委托指向 ShowMessage 方法。调用委托时,会执行 ShowMessage 方法。

    有多种类型的动作委托。例如,Action 委托封装了一个采用单个参数且不返回值的方法。

Program.cs

Action<string> act = ShowMessage;
act("C# language");

void ShowMessage(string message)
{
    Console.WriteLine(message);
}

我们修改前面的示例以使用带有一个参数的操作委托。

Action<string> act = ShowMessage;
act("C# language");

我们创建一个 Action 委托的实例并使用一个参数调用它。

9.3 C# 谓词委托
谓词是一种返回真或假的方法。谓词委托是对谓词的引用。谓词对于过滤值列表非常有用。

Program.cs

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;

我们创建一个谓词委托的实例。委托指向一个谓词,一个返回真或假的特殊方法。

 List<int> vals2 = vals.FindAll(myPred);

FindAll 方法检索与指定谓词定义的条件匹配的所有元素。

bool greaterThanThree(int x)
{
    return x > 3;
}

对于大于三的所有值,谓词返回 true。

在C#中,委托(Delegate)和事件(Event)是用于实现事件驱动编程和回调机制的两个重要概念。它们允许对象之间松耦合地通信,当一个对象的状态发生变化时,可以通知其他对象。

委托(Delegate)
委托是一种类型,它安全地封装了方法的签名和定义。委托是用作回调的一种类型,可以将方法作为参数传递或赋值给变量。委托定义了一种可以引用具有特定参数列表和返回类型的任何方法的类型。一旦为委托分配了方法,委托就可以像调用方法一样被调用。

委托的声明决定了可以引用哪种方法。委托的声明与方法的声明类似,但没有方法体,并且在定义时使用了delegate关键字。

在这个例子中,MyDelegate是一个委托类型,它表示任何没有返回值(void)并接受一个string类型参数的方法。

事件(Event)
事件是基于委托的,它们提供了一种方式来允许对象或类向其他对象或类提供通知。事件使用event关键字声明,并且其类型通常是一种委托类型。

与委托不同,事件是封装在类中的,并且只能从类的内部触发(调用)。外部代码可以订阅(添加处理程序)或取消订阅(移除处理程序)事件,但不能直接触发事件。这种限制有助于保持对象之间的松耦合,并防止未授权的代码触发事件。

委托与事件的区别
访问限制:委托可以被任何拥有委托引用的代码调用,而事件只能由定义事件的类内部触发,外部代码只能订阅或取消订阅事件。

封装性:事件提供了更好的封装性,因为外部代码不能直接触发事件,只能通过订阅来响应事件。

多播能力:委托和事件都支持多播(即可以有多个订阅者),但事件的多播是通过语言特性支持的,而委托的多播则需要手动管理。

安全性:由于事件的访问限制和封装性,使用事件通常比直接使用委托更安全,尤其是在设计大型系统和库时。

使用场景:当需要从一个类向其他类发送通知时,通常使用事件。当需要更灵活的方法调用或回调时(例如,异步编程中的回调函数),通常使用委托。

总的来说,委托和事件在C#中都是非常重要的概念,它们提供了强大的回调和事件驱动编程能力。正确选择何时使用委托和何时使用事件,有助于设计出结构良好、易于维护和扩展的软件系统。

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