【问题标题】:Handling exceptions in a class library of a plug-in architecture在插件架构的类库中处理异常
【发布时间】:2015-02-16 09:58:33
【问题描述】:

我正在开发一种插件架构,以允许通过使用适当的插件来执行异构任务,该插件将从一个或多个库(来自指定目录的 DLL 文件)加载。

首先我定义了每个插件必须实现的接口,因此我创建了以下 C# 接口。

public interface ITaskProcessor
{
    string Name { get; }
    string Version { get; }
    string Author { get; }
    string Description { get; }

    void Execute(Stream sourceStream, Stream destStream);
}

本质上,在定义这个接口时,我假设与任务相关的数据必须预先存储在一个文件中,并且结果将存储在另一个文件中。为此,Execute 方法需要两个Stream 对象作为参数。

两个流的打开和关闭是所有插件通用的操作,所以我决定在TaskProcessorContext类中执行,如下。

public class TaskProcessorContext
{
    private ITaskProcessor m_TaskProcessor;

    public TaskProcessorContext(ITaskProcessor executor)
    {
        m_TaskProcessor = executor;
    }

    public void Execute(string sourceFileName, string destFileName)
    {
        InternalExecute(sourceFileName, destFileName);
    }

    private void InternalExecute(string sourceFileName, string destFileName)
    {
        FileStream sourceStream = null;
        FileStream destStream = null;

        try
        {
            sourceStream = File.OpenRead(sourceFileName);
            destStream = new FileStream(destFileName, FileMode.Create, FileAccess.Write);

            m_TaskProcessor.Execute(sourceStream, destStream);   // invoking the method of a plug-in
        }
        catch (ArgumentNullException e)
        {
            throw new ArgumentNullException(e.ParamName, e.Message);
        }
        catch (ArgumentException e)
        {
            throw new ArgumentException(e.Message, e.ParamName, e);
        }
        catch (FileNotFoundException e)
        {
            throw new FileNotFoundException(e.Message, e.FileName, e);
        }
        catch (DirectoryNotFoundException e)
        {
            throw new DirectoryNotFoundException(e.Message, e);
        }
        catch (PathTooLongException e)
        {
            throw new PathTooLongException(e.Message, e);
        }
        catch (UnauthorizedAccessException e)
        {
            throw new UnauthorizedAccessException(e.Message, e);
        }
        catch (NotSupportedException e)
        {
            throw new NotSupportedException(e.Message, e);
        }
        catch (IOException e)
        {
            throw new IOException(e.Message, e);
        }
        catch (System.Security.SecurityException e)
        {
            throw new System.Security.SecurityException(e.Message, e);
        }
        finally
        {
            if (sourceStream != null)
            {
                sourceStream.Close();
                sourceStream.Dispose();
                sourceStream = null;
            }

            if (destStream != null)
            {
                destStream.Close();
                destStream.Dispose();
                destStream = null;
            }
        }
    }
}

我认为捕获并重新抛出在打开流期间可能发生的所有异常是合适的,将引发的异常作为内部异常传递以避免丢失堆栈跟踪。

如果两个流都正确打开,即打开流时没有出现异常,则调用某个插件的Execute方法。

显然,您无法提前知道每个插件执行的处理操作,因此甚至无法提前知道此类处理过程中可能发生的异常。但是,我需要一种方法来知道在执行插件的Execute 方法期间发生的任何错误:换句话说,我希望每个插件的Execute 方法可能会抛出额外的异常,但是TaskProcessorContext.Execute 方法应该捕获它并重新抛出它而不会丢失堆栈跟踪...

在我看来,管理此问题的唯一方法是定义一个类以用于插件抛出的异常。例如,我可以创建类TaskProcessingException,因此在特定插件的Execute 方法中发生的任何异常都应该使用这个新类重新抛出,如下例所示:

public class PluginExample : ITaskProcessor
{
    // ...

    public void Execute(Stream sourceStream, Stream destStream)
    {
        try
        {
            // ...
        }
        catch (SomeSpecificException e)
        {
            throw new TaskProcessingException(e.Message, e);
        }
        finally
        {
            // ...
        }
    }
}

因此,我还应该将以下代码添加到TaskProcessorContext 类的Execute 方法中,以便从插件中捕获TaskProcessingException

catch(TaskProcessingException e)
{
    throw new TaskProcessingException(e.Message, e);
}

这样,TaskProcessorContext 类的用户将能够捕获和管理在调用其Execute 方法期间可能发生的所有异常。

上述方法正确吗? 有其他方法吗?

更新

我修改了TaskProcessorContext.Execute方法,让ITaskProcessor接口的特定实现抛出的异常在自定义的TaskProcessingException中重新启动,这样调用者就可以知道异常发生在哪里并正确处理。

public void Execute(string sourceFileName, string destFileName)
{
    FileStream sourceStream = null;
    FileStream targetStream = null;

    try
    {
        sourceStream = File.OpenRead(sourceFileName);
        targetStream = new FileStream(destFileName, FileMode.Create, FileAccess.Write);

        try
        {
            m_TaskProcessor.Execute(sourceStream, targetStream);
        }
        catch (Exception e)
        {
            // Catch and re-throw the exceptions
            // launched from a specific plug-in.
            throw new TaskProcessingException(e.Message, e);
        }
    }
    catch
    {
        throw;
    }
    finally
    {
        if (sourceStream != null)
        {
            sourceStream.Close();
            sourceStream.Dispose();
            sourceStream = null;
        }

        if (targetStream != null)
        {
            targetStream.Close();
            targetStream.Dispose();
            targetStream = null;
        }
    }
}

【问题讨论】:

  • 你为什么要捕获所有这些异常并简单地用内部异常重新抛出它们?你有什么用?它只是用另一个异常包装了异常。
  • 你想让插件向消费者抛出错误信息吗?如果是,那么您的方法在最初的观察中看起来不错,但如果您不希望这样,那么您需要将诸如 log4net 之类的记录器与此相关联,这将记录错误信息。此外,对于您的上下文对象,我猜它也应该具有错误属性,以便消费者在处理输出之前检查是否有任何错误。
  • @Amit:是的,我希望插件向消费者抛出错误信息。
  • @YuvalItzchakov:我可以直接写catch { throw; }吗?
  • 完全没有捕获如何? :) 如果你只扔,那么抓也没有意义

标签: c# .net exception exception-handling


【解决方案1】:

恕我直言,您要求插件开发人员只抛出某种类型的异常,这给他们施加了太大的压力。

由于无法强制执行此行为,即在 pugin 执行上下文中仅抛出 TaskProcessingException,您和您的类的消费者都不应该依赖它。

另一方面,您可以引入TaskProcessingException 作为插件在执行任务(例如从流中接收到错误数据)时发出预期失败的信号的一种方式。在这种情况下,这将被视为业务异常,您的 TaskExecutionContext 的使用者将能够将其与从 TaskExecutionContext 抛出的其他异常类型区分开来。

【讨论】:

  • 其实没有办法强制实现ITaskProcessor接口的类使用特定类型的异常来抛出异常...
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-10-13
  • 2023-03-08
  • 2012-03-10
  • 1970-01-01
  • 2014-03-27
  • 1970-01-01
相关资源
最近更新 更多