关于 C# 中的事件和委托

本文关键字:事件 关于 | 更新日期: 2023-09-27 18:27:03

将事件视为委托类型的实例有什么副作用?

Jon Skeet 说,"事件不是委托实例。如果我在其他任何地方读过这篇文章,我就不会问这个问题。

在过去的 2 个月里,我一直将事件可视化为特殊类型的委托,事件关键字只是有助于防止通过事件调用代表无效。

有人可以详细说明如何为C#和基于事件的编程新手正确可视化整个概念吗?

编辑 1:

我理解了代表的概念,因此事件变成了一个非常简单的概念。我想继续添加我在与这些结构的斗争中想到的一个例子。为了更好地理解,我添加了很多评论。这是为像我这样的新人准备的:

库 DLL:

namespace DoSomethingLibrary
{
    /*
     *This is a public delegate declared at the base namespace level for global presence.
     *The question is WHY do we need to have a DELEGATE here?
     *The answer is: I do not want to implement the LOGGING logic. Why? Well, my consumers are many
     *and all are equally demanding. They all need different types of logging. Some need HTML logging, 
     *some need XML logging for their custom log analyzer, some need plain text logging etc...
     *This is hell for me. How am I going to support all their demands. I cannot. Thus, I ask them to 
     *implement LOGGING on their side. I am providing an INTERFACE(literal sense) in the guise of a DELEGATE.
     *A DELEGATE is a HOOK.
     *This is the hook that is needed for consumers to hook their custom loggers into the library.
     */
    public delegate void Logger(string firstParam, string secondParam);
    public class PrintingManiac
    {
        public Logger printingManiacConsumerLoggerHook;
        public void StartPrintingLikeAManiac()
        {
            for (int iterator = 0; iterator <= 3; iterator++)
            {
                /*This loop is an emulator which I am using to emulate some huge processing or some huge job.
                 *Let us imagine that this is a library that does some heavy data crunching OR some 
                 *extremely complex data access job etc..
                 */
                Console.WriteLine("Actual WORK - " + iterator.ToString());
                /*After each step this library tries to LOG. But NOTE that this library
                 *has no LOGGER implemented. Instead, this library has judiciously DELEGATED
                 *the logging responsibilty to the CONSUMER of this library.
                 */
                printingManiacConsumerLoggerHook("Actual Work", "Step " + iterator.ToString());
            }
        }
    }
}

使用者可执行文件:

/*
 * Let us assume that I have purchased the DoSomethingLibrary DLL from a vendor.
 * I have to add the DLL as a reference to my executable's project in Visual Studio.
 * I also have to use the DoSomethingLibrary namespace to access the Logic in the DLL.
 */
using DoSomethingLibrary;
namespace UnderstandingDelegates
{
    class Program
    {
        static void Main(string[] args)
        {
            /*
             * Creating an object of the lone class PrintingManiac in the DoSomethingLibrary
             */
            PrintingManiac newManiac = new PrintingManiac();
            /*
             * HOOKING my custom logger to the DoSomethingLibrary DLL.
             * I get the best of both the worlds. I have a well-tested and efficient library working for me
             * AND I have the best logging avaliable.
             * The DoSomethingLibrary DLL has no knowledge of what logging this executable is going to use.
             * This executable has to just satisfy the requirements of the DELEGATE signature of DoSomethingLibrary DLL.
             */
            newManiac.printingManiacConsumerLoggerHook += new Logger(ClientsCustomizedLoggerTwo);
            newManiac.StartPrintingLikeAManiac();
            Console.ReadLine();
        }
        public static void ClientsCustomizedLoggerOne(string firstParam, string secondParam)
        {
            /*
             *This logger has '=' used as a decorator
             *In real scenarios the logger may be very complex.
             *Let us assume this is an HTML logger
             */
            Console.WriteLine("=============================");
            Console.WriteLine("Delegated Logging IN CONSUMER code " + firstParam + " - " + secondParam);
            Console.WriteLine("=============================");
        }
        public static void ClientsCustomizedLoggerTwo(string firstParam, string secondParam)
        {
            /*
             *This logger has '-' used as a decorator
             *Let us assume this is an XML logger
             */
            Console.WriteLine("------------------------------");
            Console.WriteLine("Delegated Logging IN CONSUMER code " + firstParam + " - " + secondParam);
            Console.WriteLine("------------------------------");
        }
    }
}

编辑2:

我在CodeProject中写了一篇文章来清楚地解释委托的整个概念。

关于 C# 中的事件和委托

如何正确可视化整个概念

没有人在 C# 中可视化属性时遇到太多麻烦。 属性是字段的访问器,它可以防止其他代码直接操作字段值。 此类代码被迫调用 getset 访问器来访问字段。 您可以将任意代码放在访问器方法中,例如,当您对传递给 setter 的值不满意时引发异常,这很常见。 属性的基础存储也不必是字段,例如,可以公开另一个类对象的字段或属性。 诸如此类。

可视化事件的一个好方法是将其与属性进行比较。 通过完全相同的意图,它可以防止其他代码直接操作委托对象。 他们必须通过添加删除访问器。 对这些方法的调用由客户端代码中的语法糖生成,+=运算符调用 add((,-=调用 remove((。 与用于访问属性的语法糖类似,您也不会显式编写对 get 或 set 方法的调用。

事件

令人困惑并使它们看起来与属性如此不同的是,事件访问器方法是可选的。 如果不编写它们,则 C# 编译器将自动生成它们。 包括后备存储、委托对象。 属性还可以具有自动生成的访问器和后备存储,自动属性也是如此。 但是语法不同,你仍然必须声明它们。

使用自动生成的访问器非常普遍。 该代码几乎总是足够好的,它已经保证了任意代码不能删除其他代码的事件订阅。 没有那么多好的理由来写你自己的。 一个是减少类对象的大小,如果你支持很多事件。 您可以改为将它们存储在 EventHandlerList 中,而不是为每个单独的事件设置一个委托对象。 例如,在.NET框架代码中很常见。 在 WPF 的附加事件中也利用了额外的间接寻址,并且 WinRT 的事件模型不基于委托。

事件由两个称为访问器的特殊方法组成,即 addremove 。两者都接受一个value相同委托类型的值参数,并返回 void

例如,这是一个事件:

public event Action Exploded
{
  add
  {
    Console.WriteLine("Hello from 'add'. Type of 'value' is '{0}'.", value);
  }
  remove
  {
    Console.WriteLine("Hello from 'remove'. Type of 'value' is '{0}'.", value);
  }
}

"类字段"事件是编译器生成的事件类型,其中存在相关委托类型的私有生成支持字段,并且add访问器将value添加到该支持字段(委托组合(的调用列表中,而remove访问器从该列表中删除value。这是以智能线程安全的方式完成的。

我认为关于

C# 中事件的最大混淆来源之一源于这样一个事实,即未指定显式添加和删除方法的事件声明会创建一个与事件同名的委托(顺便说一句,我不喜欢这种设计;默认情况下 VB.NET 给委托一个不同的名称(。 因此,声明事件实际上foo声明名为 foo 的事件和名为 foo 的委托;名称 foo 有时指代理,有时返回到事件。

.NET 中的事件实际上只不过是一对方法,每个方法都接受一个委托。 其中一个方法称为"add",它应该使该方法在/何时/每当出现某些特定条件时调用提供的委托。 另一种方法称为"删除",应要求取消与特定委托关联的"订阅"。 请注意,有关常规事件协定的任何内容都不要求事件对传入的委托执行任何操作。 不可变集合可以实现"可观察集合"接口,但只需忽略任何添加或删除更改通知的请求 [可观察集合协定要求在集合更改时调用添加到"集合更改"事件的所有委托,但由于不存在集合实际可以更改的情况,因此不存在需要调用传入的委托的情况]。

默认情况下,当类 1 声明eventName C# 中的事件时,编译器还会声明与该事件关联的委托类型的变量eventName。 形式eventName += someDelegate的任何陈述都将翻译为eventName_add(someDelegate),表格eventName -= someDelegate的陈述将被翻译为eventName_remove(someDelegate)。 所有其他对eventName的引用将被视为对该名称的代表的引用。 请注意,在旧版本的 C# 中,如果委托在作用域内,则+=-=窗体的语句将直接对委托而不是事件进行操作。 这意味着,虽然从类外部收到的订阅请求将通过添加/删除方法(将使用锁定或互锁方法来提供线程安全(,但在类中处理的订阅请求不是线程安全的。 但是,更高版本的 C# 始终将eventName += someDelegateeventName -= someDelegate视为添加/删除请求,即使委托在范围内也是如此。