处理事件中的异常



在这个例子中,我的事件有两个订阅者。其中一个订阅者引发了异常,但我希望防止所有订阅者在只有一个订阅者出现异常时失败。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
        }
    }
}

这允许您单独调用每个订阅者,而不是作为一个组调用,并在其中一个订阅者抛出时捕获异常。我这样做的问题是,你真的不知道是什么坏了,也不知道该做什么来修复它。你所能做的就是记录并选择性地删除该事件处理程序,这样下次你就不会再使用它了。

删除处理程序也可能是一种糟糕的做法,因为很难跟踪以前分配的处理程序现在被取消分配的原因。

最新更新