调用特定事件的处理程序的顺序取决于该特定事件的实现。例如,使用多案例委托的默认后备存储,将按照它们注册的顺序调用处理程序。但是类设计者/实现者可能使用add
和remove
关键字来提供具有不同后备存储的事件访问器,因此调用顺序也会不同。
.NET框架基础库本身是否存在事件文档精确描述其调用顺序的任何情况?是否存在,依赖于这样的文件化顺序(例如我自己实施和记录的事件)是否被认为是可接受的做法?为什么或为什么不呢?
答案 0 :(得分:4)
不是我知道的。这几乎总是先到先出,因为不使用列表效率不高。 MulticastDelegate和EventHandlerList以这种方式工作。
依赖订单是有风险的。有很多情况下,程序员取消订阅事件以防止重入问题,在方法退出时再次订阅它。由于订单将发生不可避免的副作用,他的事件处理程序现在将被调用。如果这导致程序失败,那么该程序员将会困惑很长一段时间。他只是不会看到代码更改和错误行为之间的联系,这是一个非常难以修复的错误。尽管如此,让代码以意想不到的方式进行交互仍然是一个陷阱,只有好的设计才能避免它,只有好的调试器才能对其进行诊断。
答案 1 :(得分:1)
我发布这个问题的那天,我专注于一个特定的案例,其中注册顺序是明显和稳定的。但是第二天我已经注意到这是例外而不是规则。
可以通过任何代码路径在任何地方添加处理程序,因此通常无法对该顺序执行任何有用的操作。因此,一般规则是忽略注册顺序,并尝试使代码工作,无论添加/调用处理程序的顺序如何。这有时需要在您使用代码后采取预防措施。在我目前关注的情况下,这需要创建一个伴随Changing
事件,与Changed
事件配对,这样必须首先发生的事情将进入Changing
事件的处理程序。
我认为您可以记录事件的顺序调用顺序,以查看注册明显且稳定的罕见情况。但是,对于每个依赖于顺序的注册,您还需要记录它的顺序重要性,当您的代码发展时,您必须记住这些重要性,否则会出现问题。听起来很多工作,所以坚持上段提到的一般规则可能会更容易!
我可以想到一种可靠地控制调用顺序的可行方法。您可以将优先级作为处理程序注册的一部分传递。这样,您不,具体取决于顺序注册顺序。但是 控制相对调用顺序。但是,这样的实现更重且非标准,因此在大多数情况下可能并不理想。
答案 2 :(得分:0)
我刚刚遇到一个我认为在编写测试时完全可以接受的场景。考虑这个辅助类来异步加载某些东西并在操作完成时通过事件通知:
public class AsyncItemLoader<T>
{
/// <summary>
/// Handlers will be invoked in registration order, pinky-swear!
/// Also, they are invoked on a thread from the ThreadPool.
/// </summary>
public EventHandler<T> ItemLoaded;
public void LoadAsync()
{
// load the item asynchronously using Task.ContinueWith to fire
// ItemLoaded event to indicate that the item is now available
}
}
现在假设我们在某个模型类中使用这个帮助器:
public class MyAppModel
{
private readonly AsyncItemLoader<User> userLoader;
public AsyncItemLoader<User> User { get { return userLoader; } }
public MyAppModel()
{
this.userLoader = new AsyncItemLoader<User>(...);
this.userLoader += HandleUserLoaded;
}
public void StartLoadingUser()
{
userLoader.LoadAsync();
}
private void HandleUserLoaded(object sender, User user)
{
// do something with the user here
}
}
好的,现在我们要为MyAppModel编写一个测试,它依赖于HandleUserLoaded在调用StartLoadingUser后的某个时间点所做的事情,比如检查对模拟依赖项的某些方法的调用。
在检查我们的模拟方法的调用之前,我们如何确定地等待HandleUserLoaded完成处理? 很容易,因为事件被记录为按注册顺序调用处理程序,我们知道MyAppModel在客户端有机会注册它们之前注册自己的处理程序:
public class MyAppModelTest
{
[Test]
public void StartLoadingUserCausesMyAppModelToDoStuffWithOtherDep()
{
var model = new MyAppModel();
var itemLoaded = new ManualResetEventSlim(initialState: false);
model.User.ItemLoaded += (s, e) => itemLoaded.Set();
model.StartLoadingUser();
itemLoaded.Wait();
// At this point we are guaranteed that MyAppModel.HandleUserLoaded
// has finished execution
myServiceMock.Received().AmazingServiceCall();
}
}