【发布时间】:2015-09-02 22:47:08
【问题描述】:
引发事件,将调用其事件处理程序。例如http://msdn.microsoft.com/en-us/library/aa645739%28VS.71%29.aspx
使用事件机制和直接调用其他方法有什么区别(例如,如果方法A()中满足条件,则调用B())?
消费事件和引发事件有什么区别?
谢谢
【问题讨论】:
标签: c#
引发事件,将调用其事件处理程序。例如http://msdn.microsoft.com/en-us/library/aa645739%28VS.71%29.aspx
使用事件机制和直接调用其他方法有什么区别(例如,如果方法A()中满足条件,则调用B())?
消费事件和引发事件有什么区别?
谢谢
【问题讨论】:
标签: c#
区别是这样的:
方法调用 = "做这件事"
事件引发 = "如果有人在倾听和关心,这件事就发生了。"
它是关注点分离和可重用性的核心。如果单击按钮调用特定方法,则按钮不是可重用组件。但如果它只是简单地“宣布”它被点击的程序,并且相关方负责订阅它,那么它是无限可重用的。
如何实现(通过委托)的底层技术实现无关紧要。
【讨论】:
引发一个事件,将调用它的事件 处理程序
一开始就错了。可能有 no 事件处理程序。或者很多。你不知道。这是直接调用方法的主要区别。在您最喜欢的设计模式书籍中查找“观察者模式”。
【讨论】:
使用和使用有什么区别 事件机制和直接调用 到其他方法(例如,如果条件是 在方法 A() 中遇到,调用 B() )?
从业务逻辑上讲,两者之间没有区别。我的意思是,您可以通过每种方式完成相同的任务。这只是一种不同的方式。真正的区别在于您必须做的工作量来处理其他模块的通知。
通过引发事件,您实际上是在说“嘿,发生了什么事,任何已注册以在发生这种情况时得到通知的代码,让他们知道。哪些模块得到通知不是我关心的,因为我是假设(在运行时)所有需要知道的模块都设置为通知。”
通过直接调用每个方法,您将决定告诉这个(或这些)模块,并且只告诉这些模块发生了某些事情。您是在断言,无论这些模块处于什么状态都不重要,它们需要知道这个事件发生了。
两者都适用于不同的情况。事件通知更加动态。不同的模块可以注册和注销通知。直接方法调用更加静态。某些对象(或模块等)绝对会被通知(当然,除非例外)发生了某事,但只会通知这些对象。
【讨论】:
引发事件(或调用,使用链接中的术语)意味着您将事件发送给所有消费者。例如,一个窗口可以在鼠标点击时引发一个事件。
使用事件意味着您正在从发送事件的人那里接收和处理事件。例如,您可能想知道鼠标单击窗口的时间。
如果你只有一个消费者,那么你可以通过直接提供回调来完成类似的事情:
// '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。就像您参加活动一样。
对于任何对事件调用性能感兴趣的人,我做了这个简单的基准测试。 它显示了直接调用方法、通过接口、通过委托和通过事件调用方法之间的区别,其中附加了一个处理程序。
在每个场景中,该方法以相应的方式被调用 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;
}
}
【讨论】:
除了上面的多订阅者/无订阅者场景之外,事件还被用来减少代码耦合——例如,方法 A() 在编译时不需要知道关于方法 B() 的任何信息。这样可以更好地分离关注点并减少脆弱的代码。
在野外,您更有可能看到在框架和 UI 代码中使用的事件,而在应用程序的域逻辑中,开发人员更经常使用 Separated Interface 和 Dependency Injection 之类的东西来解耦代码。最近在各个领域都有更多关于在域逻辑中使用事件的讨论,这种方法被巧妙地命名为Domain Events。
【讨论】: