C# 如何处理可以抛出任何东西的接口?

C# 如何处理可以抛出任何东西的接口?,c#,exception-handling,error-handling,C#,Exception Handling,Error Handling,我经常发现自己处于这样一种情况:我必须捕获接口实现者抛出的一些异常。当不同的实现处理完全不同类型的设备等时,这通常会出现问题:根据实现的不同,抛出的异常的数量和类型会有很大的不同。下面是一个典型的例子: interface DataStream { Data ReadNext(); } class DeserializingFileDataStream : DataStream { //this one does file operations + serialization, /

我经常发现自己处于这样一种情况:我必须捕获接口实现者抛出的一些异常。当不同的实现处理完全不同类型的设备等时,这通常会出现问题:根据实现的不同,抛出的异常的数量和类型会有很大的不同。下面是一个典型的例子:

interface DataStream
{
  Data ReadNext();
}

class DeserializingFileDataStream : DataStream
{
  //this one does file operations + serialization,
  //so can throw eg IOException, SerializationException, InvalidOperationException, ...
}

class NetworkDataStream : DataStream
{
  //get data over tcp
  //so throws IOException, SocketException
}

class HardwareDeviceDataStream : DataStream
{
  //read from a custom hardware device implemented in unmanaged code
  //so throws mainly custom exceptions
}
很可能所有这些都会抛出ArgumentException等等,但我对捕捉它们不感兴趣:在这种情况下,它们会指示编程错误。但是,我不希望程序在文件损坏、网线断开或自定义设备崩溃时崩溃,因此应处理其他异常

我已经尝试过几种解决方案,但没有一种是我特别满意的,所以问题是:是否有一些常见的做法/模式来处理类似的情况?请具体一点,不要让我“看艾玛”。以下是我过去使用过的一些东西:

  • catch(Exception)
    很明显,问题是什么
  • TryAndCatchDataStream(Action what,Action errHandler)
    方法,包括一个try,后跟一个捕获任何实现中感兴趣的任何异常的方法。这意味着在实现更改或添加/删除时,必须对其进行更新
  • 在接口上添加一条注释,说明“应该只抛出DataStreamException”,并在所有实现中确保遵循此规则,捕获感兴趣的任何内容并将其包装到DataStreamException中,然后抛出该规则。还不算太坏,但会给每个实现增加噪音
然后我有第二个关于异常处理的更一般的问题(不认为需要对此单独发表文章):我有一些情况,方法a调用B调用C调用D,D抛出SomeException,但a的调用方捕获了异常。这不是代码有问题的迹象吗?因为要做到这一点,A的调用方需要知道A最终会调用D,除非A记录了它可以抛出SomeException;但在这两种情况下,这意味着当更新a的一个实现细节时(即使其调用D以外的东西),a的用户可以看到此更改。

无法“知道”未知异常可能是什么。你所能做的就是捕获你知道的异常,也许记录你不知道的异常并重新显示它们

您可以捕获异常,然后在一个单独的程序集中调用一个方法,该方法确定如何处理Exception。然后,您可以根据需要更新程序集,而无需更新其余代码

关于第二个问题,A需要记录任何可能从中冒出的异常,包括依赖对象引发的异常。B需要记录抛出的异常,这样A就可以知道这些异常,等等。。等等

如果您更改了一个,以执行导致抛出的异常发生更改的操作,那么您必须更改文档。从呼叫应用程序的角度来看,它只知道A。A可以做任何事情,而呼叫应用程序并不关心它是A B、C还是D。A负责向呼叫方提供的任何信息


这样想吧。假设你雇了一家建筑公司给你盖房子。他们反过来雇用分包商。这些次级承包商可能雇佣自己的劳动力。如果底层劳工搞砸了,不管是不是其他公司雇佣了他们,你雇佣的承包商都有错。你不在乎,你只是希望你的房子按规格建造,而建筑公司对此负责。

你可以实施自己的定制例外。如果接口捕获自己的异常并在将实际异常设置为内部异常时抛出自定义异常,则让每个实现都执行。根据你的情况可能没问题

因为例外情况应该是例外情况。这样,您至少可以知道是否捕获了实现知道要监视的异常


我不确定这是不是个好主意。我想我会提供这个想法。

Java迫使您声明抛出哪些异常,这是一个非常痛苦的过程,几乎没有什么好处——通常是因为异常是在异常情况下抛出的,很难提前计划

Java程序员经常做的事情(在被教导不要使用
抛出异常后)是您的第三个选择。第三个选项对于您不希望调用代码知道如何处理的异常是有意义的-如果您希望调用代码处理它们(例如,在发生IOException或NetworkException时重复三次),您最好坚持人们已经知道的


因此,简而言之,我建议您在调用代码中捕获您知道如何处理的异常,并报告您无法处理和退出(或操作失败)的异常.

您可以使用抽象类而不是接口在基类中封装来自各种实现的异常,以便能够以通用方式封装底层异常。这样,您就有了一个中心点来改变异常包装和分析逻辑。有些异常可能是严重的,这仍然会导致进程终止,比如OutOfMemoryException,在这种情况下继续下去很可能是不安全的

如果您坚持使用接口,您应该等到将来的CLR版本,在该版本中,接口可以有一个基本实现。有一段视频谈到了这样一个功能,但我再也没有听说过

如果您不能等待CLR vNext,那么您可以选择创建一个包装类(我们称之为DataStreamReader),该包装类接受一个IDataStream接口,该接口也实现IDataStream,但按照您的意愿进行包装。如果你小心点的话
   class Data { }
    public class DataStreamException : Exception
    {
        public DataStreamException(string message, Exception inner)
            : base(message, inner)
        {   }
    }

    abstract class DataStream
    {
        protected abstract Data ReadNextImpl();
        public Data ReadNext()
        {
            try
            {
                return ReadNextImpl();
            }
            catch (Exception ex)
            {
                throw new DataStreamException("Could not read from stream. See inner exception for details.", ex);
            }
        }
    }

    class DeserializingFileDataStream : DataStream
    {
        protected override Data ReadNextImpl()
        {
            throw new NotImplementedException();
        }

    }

    class NetworkDataStream : DataStream
    {
        protected override Data ReadNextImpl()
        {
            throw new Exception();
        }
    }
... before doing anything that might cause object to enter invalid state even temporarily ... if (state != MyClassState.HappyIdle) throw new StateCorruptException(....); state = MyClassState.UnhappyOrBusy; ... manipulate state in a fashion that will end up with a valid state when complete ... state = MyClassState.HappyIdle;