在一个插件架构的class库中处理异常

Handling exceptions in a class library of a plug-in architecture

我正在开发一种插件架构,允许通过使用适当的插件执行异构任务,这些插件将从一个或多个库(来自指定目录的 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 对象作为参数。

这两个流的打开和关闭是所有插件共有的操作,所以我决定在classTaskProcessorContext中执行,如下

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 方法应该捕获它并在不丢失堆栈跟踪的情况下重新抛出它...

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

public class PluginExample : ITaskProcessor
{
    // ...

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

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

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

这样,TaskProcessorContext class 的用户将能够捕获和管理在调用其 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;
        }
    }
}

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

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

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

抛出的其他异常类型区分开来