0

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

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

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

有人可以详细说明如何为 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中写了一篇文章,清楚地解释了代表的整个概念。

4

3 回答 3

3

如何正确地可视化整个概念

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

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

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

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

于 2013-08-10T15:16:58.517 回答
2

一个事件由两个称为访问器的特殊方法组成,即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从该列表中删除。这是以智能线程安全的方式完成的。

于 2013-08-10T14:37:04.257 回答
1

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

.NET 中的事件实际上只不过是一对方法,每个方法都接受一个委托。其中一种方法称为“add”,如果/何时/何时出现某些特定条件,则应使该方法引起所提供的委托被调用。另一种方法称为“删除”,应该要求取消与特定委托关联的“订阅”。请注意,一般事件合约的任何内容都不需要事件对传入的委托做任何事情。不可变集合可以实现“可观察集合”接口,但只需忽略任何添加或删除更改通知的请求[可观察集合合同将要求在集合更改时调用添加到“集合更改”事件的所有委托,

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

于 2013-08-11T21:00:21.647 回答