如何指定接口的实现者抛出的异常?
Posted
技术标签:
【中文标题】如何指定接口的实现者抛出的异常?【英文标题】:How to specify exceptions to be thrown by an implementor of an interface? 【发布时间】:2011-04-24 19:21:26 【问题描述】:我目前正在开发一种解决方案,并以一种能够强有力地实现策略/提供者模式的方式对其进行设计。因此,该解决方案公开了许多接口并包含这些接口的默认实现,这些接口可以通过 DI 类型方法进行替换。
如果宿主应用程序使用许多这些接口,它期望处理可能发生的某些异常,例如IDataRetriever
接口有一个方法SomeDataType GetData(int timeout);
,并且宿主可以处理一些自定义异常,例如DataRetrievalTimeoutException
或NetworkConnectionException
。
我的问题是,标记接口类的最佳方法是什么,以便开发人员在实现它时知道应该抛出某些异常并由主机处理?
目前我刚刚将异常 xml 标记添加到方法 xml 注释中 - 这足够了吗?
【问题讨论】:
【参考方案1】:XML 标记(以及您想要编写的任何其他文档)基本上是目前“vanilla”.NET 中最接近的。
您可能想查看Code Contracts,它可以让您使用合同注释您的界面,其中可以包括异常、先决条件等。
【讨论】:
请注意,链接现在应该指向github.com/Microsoft/CodeContracts【参考方案2】:您不能在界面中。你可以在基类中。
public interface IFoo
/// <summary>
/// Lol
/// </summary>
/// <exception cref="FubarException">Thrown when <paramref name="lol">
/// is <c>null</c></exception>
/// <remarks>Implementors, pretty please throw FE on lol
/// being null kthx</remarks>
void Bar(object lol);
对比
public abstract BaseFoo
/// <summary>
/// Lol
/// </summary>
/// <exception cref="FubarException">Thrown when <paramref name="lol">
/// is <c>null</c></exception>
public void Bar(object lol)
if(lol == null)
throw new FubarException();
InnerBar(lol);
/// <summary>
/// Handles execution of <see cref="Bar" />.
/// </summary>
/// <remarks><paramref name="lol"> is guaranteed non-<c>null</c>.</remarks>
protected abstract void InnerBar(object lol);
【讨论】:
如果要确保Bar
不会泄漏任何意外异常类型,我认为非虚拟Bar
方法需要将对InnerBar
的调用包装在try-catch 中块,它应该捕获并包装除接口中指定的异常之外的任何异常。【参考方案3】:
我建议在接口中定义异常类,并指定除非 CPU 着火或存在其他此类严重情况(即使那样,它可能不是一个明确定义 IWoozle.SystemCorruptionException
是个坏主意,这样如果 Pokemon 处理程序只是捕获、记录并抛出异常,日志就会反映出至少有人认为异常很重要)。
我认为 Microsoft 建议避免定义自定义异常类型是不幸的,因为这意味着没有明确的方法来区分 IEnumerator<T>.MoveNext()
抛出 InvalidOperationException
是因为在枚举期间更改了底层集合,还是内部处理IEnumerator<T>.MoveNext()
遇到 InvalidOperationException
并简单地让它冒泡给调用者。相反,如果IEnumerator<T>.MoveNext()
在前一种情况下抛出IEnumerator.InvalidatedEnumeratorException
,那么任何逃逸的InvalidOperationException
都必须代表后一种情况。
【讨论】:
这听起来像是 OP 正在寻找的东西,但是你没有说 如何 去做。【参考方案4】:这不应该取决于实现接口的任何类吗?
例如,如果我使用您的接口并使用 GetData 方法实现我自己的类,我可以从任何地方“获取”数据。假设它是一个 Web 服务,那么如果我从本地文件系统“获取”数据,那么可能引发的异常类型可能会有所不同。
因此,在我的实现中,我将实现(并记录)那些特定于实现的异常,以便使用该实现的任何代码都可以处理它们。这些异常的处理方式可能与实现非常具体,比如我在 Web 应用而不是桌面应用中使用它们。
【讨论】:
接口的目的是让不同的实现可以互换。如果从AcmeDatabase
读取数据的IEnumerable/IEnumerator
的实现允许某种类型的AcmeDatabaseException
逃逸,那么调用者将无法知道这是否代表暂时的通信中断,或者这是否意味着CPU着火了。 IEnumerable
合约未能提供任何有用的指导,唉,但这不是其他接口不应该做得更好的理由,例如指定...
...任何无法完成但保持系统状态不变的IWoozle.wozzle
的执行尝试应抛出IWoozle.CleanFailureException
或其派生词。如果IWoozle
的状态可能已经改变,但从IWoozle
的角度来看可能有效,IWoozle.PossibleChangeException
。如果IWoozle
的状态可能已损坏,但损坏仅限于该对象,IWoozle.CorruptObjectedException
。除非 CPU 着火或存在其他此类严重情况,否则不要让上述以外的任何异常逃逸。
好的,我明白你的意思了。所以基本上,如果我采用 Gouldos 接口并实现了 GetData,我在幕后编写了代码以通过 Web 服务检索数据,并且发生了一些特定于 Web 服务的异常:-),那么我将不得不汇总这个异常并传递任何细节通过提出 NetworkConnectionException 回来?然后调用者将依赖于通过内部异常属性检索有关底层异常的信息....类似的东西?
没错。使用接口的原因之一是允许调用者对对象做某些事情而不必担心它的细节。如果某些代码调用 IDataRetriever.GetData
并且它无法获取它,那么可能会有人在某处关心它是否因为“未找到 DNS 主机”或“无法访问 ICMP 主机”而失败,但立即调用代码可能没有任何线索表明对象正在尝试从 Internet 获取数据,并且不知道这些条件中的任何一个可能有何不同。【参考方案5】:
我认为提供此信息的最佳方式是在您将为每个接口提供的 XML 文档中。您可以在此处指定该方法抛出的异常,以便主机可以处理该错误。
【讨论】:
【参考方案6】:如果您不能支持泛型基类,另一种策略是扩展方法实现。
public static void ThisMethodShouldThrow(this Iinterface obj)
if(obj.ConditionToThrowIsMet) throw new...
这样做的好处是您不需要继承链。
【讨论】:
以上是关于如何指定接口的实现者抛出的异常?的主要内容,如果未能解决你的问题,请参考以下文章