在一个插件架构的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
.
抛出的其他异常类型区分开来
我正在开发一种插件架构,允许通过使用适当的插件执行异构任务,这些插件将从一个或多个库(来自指定目录的 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
.