处理事件中的异常
本文关键字:异常 处理事件 | 更新日期: 2023-09-27 18:25:30
在这个例子中,我的事件有两个订阅者。其中一个订阅者引发了异常,但我希望防止所有订阅者在只有一个订阅者出现异常时失败。try-catch语句不足以捕获Dog类的异常,它也会使Cat类失败。
using System;
namespace EventsExample
{
class BreadWinnerEventArgs : EventArgs
{
public string Name { get; set; }
}
class BreadWinner // publisher
{
public event EventHandler<BreadWinnerEventArgs> ArrivedHome; // 2.
public void Action(BreadWinnerEventArgs args)
{
Console.WriteLine("Papa says: I'm at home!");
OnArriveHome(args);
}
protected virtual void OnArriveHome(BreadWinnerEventArgs args)
{
if (ArrivedHome != null)
{
foreach (EventHandler<BreadWinnerEventArgs> handler in ArrivedHome.GetInvocationList())
{
try
{
var t = ArrivedHome; // publisher uses sames signature as the delegate
if (t != null)
t(this, args);
}
catch (Exception e)
{
Console.WriteLine("Error in the handler {0}: {1}", handler.Method.Name, e.Message);
}
}
}
}
}
class Dog
{
public void OnArrivedHome(object source, BreadWinnerEventArgs e)
{
throw new Exception();
Console.WriteLine(String.Format("Dog says: Whoof {0}!", e.Name));
}
}
class Cat
{
public void OnArrivedHome(object source, BreadWinnerEventArgs e)
{ Console.WriteLine(String.Format("Cat hides from {0}", e.Name)); }
}
class Program
{
static void Main(string[] args)
{
BreadWinner papa = new BreadWinner(); // publisher
Dog dog = new Dog(); // subscriber
Cat cat = new Cat();
papa.ArrivedHome += dog.OnArrivedHome; // subscription
papa.ArrivedHome += cat.OnArrivedHome;
papa.Action(new BreadWinnerEventArgs() { Name = "Papa" });
Console.Read();
}
}
}
你几乎做到了,你只是在应该使用handler
的地方使用t
,你也在应该使用t
的地方使用ArrivedHome
。我还修改了代码以封装所有异常,然后将它们调用到自定义异常的委托将它们封装在加积异常中,并让代码引发该异常。
protected virtual void OnArriveHome(BreadWinnerEventArgs args)
{
var t = ArrivedHome; // publisher uses sames signature as the delegate
if (t != null)
{
var exceptions = new List<Exception>();
foreach (EventHandler<BreadWinnerEventArgs> handler in t.GetInvocationList())
{
try
{
try
{
handler(this, args);
}
catch (Exception e)
{
Console.WriteLine("Error in the handler {0}: {1}", handler.Method.Name, e.Message);
throw new DelegateException(handler, e, this, args); //Throw the exception to capture the stack trace.
}
}
catch (DelegateException e)
{
exceptions.Add(e);
}
}
if (exceptions.Count > 0)
{
throw new AggregateException(exceptions);
}
}
}
///Elsewhere
sealed class DelegateException : Exception
{
public Delegate Handler { get; }
public object[] Args { get; }
public DelegateException(Delegate handler, Exception innerException, params object[] args) : base("A delegate raised an error when called.", innerException)
{
Handler = handler;
Args = args;
}
}
然而,我认为你真的不应该这样做,这偏离了"预期行为",如果其他程序员不得不使用你的类来做这件事,他们可能会措手不及。
我并不是说你应该这样做,但这是一种处理方法:
protected virtual void OnArriveHome(BreadWinnerEventArgs args)
{
var handler = ArrivedHome;
if (handler == null)
return;
foreach (var subscriber in handler.GetInvocationList())
{
try
{
subscriber(this, args);
}
catch (Exception ex)
{
//You can, and probably should, remove the handler from the list here
}
}
}
这允许您单独调用每个订阅者,而不是作为一个组调用,并在其中一个订阅者抛出时捕获异常。我这样做的问题是,你真的不知道是什么坏了,也不知道该做什么来修复它。你所能做的就是记录并选择性地删除该事件处理程序,这样下次你就不会再使用它了。
删除处理程序也可能是一种糟糕的做法,因为很难跟踪以前分配的处理程序现在被取消分配的原因。