提升事件与直接方法调用差异

时间:2010-04-18 00:52:28

标签: c#

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

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

消费和举起活动有什么区别?

由于

6 个答案:

答案 0 :(得分:21)

区别在于:

方法调用 =“执行此特定操作”

事件提升 =“如果有人在倾听和关心,这件事就发生了。”

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

如何实现(通过委托)的基础技术实现是无关紧要的。

答案 1 :(得分:6)

  

举办活动,将召集活动   处理

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

答案 2 :(得分:2)

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

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

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

// '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链接在一起。

答案 3 :(得分:2)

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

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

代表电话:23 240毫秒 - 最快的

事件电话:23 295 ms

直接电话:23 396 ms

接口调用: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;
    }
}

答案 4 :(得分:1)

  

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

业务逻辑明智,两者之间没有区别。我的意思是你可以单程完成同样的任务。这只是一种不同的方式。真正的区别在于您处理其他模块通知所需的工作量。

通过举起一个活动,你实际上是在说“嘿,发生了一些代码已经注册,当发生这种情况时会收到通知,让他们知道。哪些模块得到通知并不是我的关注,因为我是假设(在运行时)所有需要知道的模块都设置为通知。“

通过直接调用每个方法,您决定要告诉这个(或这些)模块,只有这些模块已经发生了什么。你正在断言,无论这些模块的状态如何都不重要,他们需要知道这个事件发生了。

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

答案 5 :(得分:0)

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

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