【问题标题】:Raising events vs direct method calls differences引发事件与直接方法调用的区别
【发布时间】:2015-09-02 22:47:08
【问题描述】:

引发事件,将调用其事件处理程序。例如http://msdn.microsoft.com/en-us/library/aa645739%28VS.71%29.aspx

使用事件机制和直接调用其他方法有什么区别(例如,如果方法A()中满足条件,则调用B())?

消费事件和引发事件有什么区别?

谢谢

【问题讨论】:

    标签: c#


    【解决方案1】:

    区别是这样的:

    方法调用 = "做这件事"

    事件引发 = "如果有人在倾听和关心,这件事就发生了。"

    它是关注点分离和可重用性的核心。如果单击按钮调用特定方法,则按钮不是可重用组件。但如果它只是简单地“宣布”它被点击的程序,并且相关方负责订阅它,那么它是无限可重用的。

    如何实现(通过委托)的底层技术实现无关紧要。

    【讨论】:

      【解决方案2】:

      引发一个事件,将调用它的事件 处理程序

      一开始就错了。可能有 no 事件处理程序。或者很多。你不知道。这是直接调用方法的主要区别。在您最喜欢的设计模式书籍中查找“观察者模式”。

      【讨论】:

        【解决方案3】:

        使用和使用有什么区别 事件机制和直接调用 到其他方法(例如,如果条件是 在方法 A() 中遇到,调用 B() )?

        从业务逻辑上讲,两者之间没有区别。我的意思是,您可以通过每种方式完成相同的任务。这只是一种不同的方式。真正的区别在于您必须做的工作量来处理其他模块的通知。

        通过引发事件,您实际上是在说“嘿,发生了什么事,任何已注册以在发生这种情况时得到通知的代码,让他们知道。哪些模块得到通知不是我关心的,因为我是假设(在运行时)所有需要知道的模块都设置为通知。”

        通过直接调用每个方法,您将决定告诉这个(或这些)模块,并且只告诉这些模块发生了某些事情。您是在断言,无论这些模块处于什么状态都不重要,它们需要知道这个事件发生了。

        两者都适用于不同的情况。事件通知更加动态。不同的模块可以注册和注销通知。直接方法调用更加静态。某些对象(或模块等)绝对会被通知(当然,除非例外)发生了某事,但只会通知这些对象。

        【讨论】:

          【解决方案4】:

          引发事件(或调用,使用链接中的术语)意味着您将事件发送给所有消费者。例如,一个窗口可以在鼠标点击时引发一个事件。

          使用事件意味着您正在从发送事件的人那里接收和处理事件。例如,您可能想知道鼠标单击窗口的时间。

          如果你只有一个消费者,那么你可以通过直接提供回调来完成类似的事情:

          // 'Event' type:
          delegate void DelMyEvent();
          // consumer:
          class Consumer
          {
              Producer _theProducer;
              void RegisterForNotification()
              {
                 _theProducer.OnMyEvent = new DelMyEvent(OnMyEvent);
              }
              void OnMyEvent() { }
          }
          // producer:
          class Producer
          {
             public DelMyEvent OnMyEvent;
             void SendNotification()
             {
                if( OnMyEvent != null ) OnMyEvent();
             }
          }
          

          事件机制通过阻止消费者直接设置委托值来稍微清理一下。相反,它使消费者使用+= 操作符注册自己。当第一个消费者注册时,委托被设置,当第二个消费者注册时,他们的两个回调被Delegate.Combine链接在一起。

          【讨论】:

          • “如果你只有一个消费者,那么原则上你可以使用委托而不是事件”,为什么这是选择委托而不是事件的标准?,甚至很多人都可以注册到委托!
          • 这篇文章不正确。 event 只是一个修饰符,而不是一个类型。 event 实际上是 delegate,但有一些附加限制。不同之处在于事件不允许您直接设置它。 IE:myDelegate = someOtherDelegate; 会给你一个错误。事件限制访问,因此您只能使用 +=-= 运算符。一个委托也可以有一个调用列表。您可以根据需要从+= 到多个delegates 到一个delegate。就像您参加活动一样。
          • 没错。已尝试修复损坏。
          • 谢谢,这也有助于我的理解!
          【解决方案5】:

          对于任何对事件调用性能感兴趣的人,我做了这个简单的基准测试。 它显示了直接调用方法、通过接口、通过委托和通过事件调用方法之间的区别,其中附加了一个处理程序。

          在每个场景中,该方法以相应的方式被调用 1 000 000 000 次。以下是(可能令人惊讶的)结果:

          代表调用:23 240 毫秒 - 最快

          事件调用:23 295 毫秒

          直接调用:23 396 毫秒

          接口调用:23 716 ms - 最慢

          这些测量是在 .NET4.0 中使用 C# 在发布版本中完成的。

          代码在这里:

          class Program
          {
              static void Main(string[] args)
              {
                  TestClass.RunTest();
                  Console.ReadLine();
              }
          }
          
          interface ITestClass
          {
              void TestMethod(object sender, TestEventArgs eventErgs);
          }
          
          class TestClass : ITestClass
          {
              #region Events
          
              event EventHandler<TestEventArgs> TestEvent;
          
              #endregion
          
              #region Constructor
          
              public TestClass()
              {
                  TestEvent += TestMethod;
              }
          
              #endregion
          
              #region Public Methods
          
              public static void RunTest()
              {
                  int testCount = 1000000000; //1 000 000 000
          
                  string format = "{0:### ### ### ##0}";
          
                  #region Direct Call
          
                  Console.WriteLine("Direct call");
                  TestClass testClass = new TestClass();
          
                  testClass.TestMethod(testClass, new TestEventArgs(3));
          
                  Stopwatch stopwatch = Stopwatch.StartNew();
                  for (int i = 0; i < testCount; ++i)
                  {
                      testClass.TestMethod(testClass, new TestEventArgs(3));
                  }
                  stopwatch.Stop();
                  Console.WriteLine(string.Format(format, stopwatch.ElapsedMilliseconds));
                  Console.WriteLine();
          
                  #endregion
          
                  #region Interface Call
          
                  Console.WriteLine("Interface call");
                  ITestClass itestClass = new TestClass();
                  itestClass.TestMethod(testClass, new TestEventArgs(3));
          
                  stopwatch = Stopwatch.StartNew();
                  for (int i = 0; i < testCount; ++i)
                  {
                      itestClass.TestMethod(testClass, new TestEventArgs(3));
                  }
                  stopwatch.Stop();
                  Console.WriteLine(string.Format(format, stopwatch.ElapsedMilliseconds));
                  Console.WriteLine();
          
                  #endregion
          
                  #region Delegate Call
          
                  Console.WriteLine("Delegate call");
                  TestClass delegateTestClass = new TestClass();
                  Action<object, TestEventArgs> delegateMethod = delegateTestClass.TestMethod;
                  delegateMethod(testClass, new TestEventArgs(3));
          
                  stopwatch = Stopwatch.StartNew();
                  for (int i = 0; i < testCount; ++i)
                  {
                      delegateMethod(testClass, new TestEventArgs(3));
                  }
                  stopwatch.Stop();
                  Console.WriteLine(string.Format(format, stopwatch.ElapsedMilliseconds));
                  Console.WriteLine();
          
                  #endregion
          
                  #region Event Call
          
                  Console.WriteLine("Event call");
                  TestClass eventTestClast = new TestClass();
                  eventTestClast.TestEvent(testClass, new TestEventArgs(3));
          
                  stopwatch = Stopwatch.StartNew();
                  for (int i = 0; i < testCount; ++i)
                  {
                      eventTestClast.TestEvent(testClass, new TestEventArgs(3));
                  }
                  stopwatch.Stop();
                  Console.WriteLine(string.Format(format, stopwatch.ElapsedMilliseconds));
                  Console.WriteLine();
          
                  #endregion
              }
          
              #endregion
          
              #region ITestClass Members
          
              public void TestMethod(object sender, TestEventArgs e)
              {
                  e.Result = e.Value * 3;
              }
          
              #endregion
          }
          
          class TestEventArgs : EventArgs
          {
              public int Value { get; private set; }
          
              public int Result { get; set; }
          
              public TestEventArgs(int value)
              {
                  Value = value;
              }
          }
          

          【讨论】:

            【解决方案6】:

            除了上面的多订阅者/无订阅者场景之外,事件还被用来减少代码耦合——例如,方法 A() 在编译时不需要知道关于方法 B() 的任何信息。这样可以更好地分离关注点并减少脆弱的代码。

            在野外,您更有可能看到在框架和 UI 代码中使用的事件,而在应用程序的域逻辑中,开发人员更经常使用 Separated InterfaceDependency Injection 之类的东西来解耦代码。最近在各个领域都有更多关于在域逻辑中使用事件的讨论,这种方法被巧妙地命名为Domain Events

            【讨论】:

            • 虽然使用事件来减少代码耦合通常是正确的,但我认为值得注意的是,有时情况并非如此。我不时看到程序员使用事件而不是直接函数调用,因为他们认为这是更好的关注点分离,即使事件显然只用于在一个位置调用一个函数。你最终会得到两个耦合的对象,对于不经意的观察者来说,它们看起来像是解耦的对象。
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2017-01-31
            • 2014-03-05
            相关资源
            最近更新 更多