Как указать исключения, которые будут создаваться разработчиком интерфейса?

В настоящее время я разрабатываю решение и спроектировал его таким образом, чтобы оно строго реализовывало шаблон стратегии/поставщика. Таким образом, решение предоставляет ряд интерфейсов и содержит реализации этих интерфейсов по умолчанию, которые можно заменить с помощью методологии типа DI.

Если хост-приложение использует несколько таких интерфейсов, ожидается, что оно будет обрабатывать определенные исключения, которые могут возникнуть, например, интерфейс IDataRetriever имеет метод SomeDataType GetData(int timeout);, а хост может обрабатывать некоторые настраиваемые исключения, такие как DataRetrievalTimeoutException или NetworkConnectionException.

Мой вопрос: как лучше всего разметить класс интерфейса, чтобы, когда разработчик его реализует, он знал, что определенные исключения должны быть выброшены и будут обрабатываться хостом?

На данный момент я только что добавил теги исключения xml в комментарий xml методов - этого достаточно?


person gouldos    schedule 13.10.2010    source источник


Ответы (6)


XML-теги (и любая другая документация, которую вы хотите написать) в основном ближе всего к «ванильному» .NET на данный момент.

Возможно, вам стоит взглянуть на Контракты кода, которые позволяют аннотировать интерфейс контрактами, которые могут включать исключения, предварительные условия и др.

person Jon Skeet    schedule 13.10.2010
comment
Обратите внимание, что теперь ссылка должна указывать на github.com/Microsoft/CodeContracts. - person Josh; 14.02.2017

Вы не можете в интерфейсе. Вы МОЖЕТЕ в базовом классе.

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);
}

vs.

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);
}
person Community    schedule 13.10.2010
comment
Если вы хотите убедиться, что Bar не пропускает какие-либо неожиданные типы исключений, я думаю, что невиртуальный метод Bar должен обернуть вызов InnerBar в блок try-catch, который должен перехватывать и обертывать любые исключения, кроме тех, которые указаны в интерфейс. - person supercat; 03.02.2012

Я бы предложил определить классы исключений в интерфейсе и указать, что никакие исключения, которые не являются производными от них, не должны быть разрешены, если только ЦП не горит или не существует другой такой серьезной ситуации (даже в этом случае может быть неплохой идеей иметь явно определенный IWoozle.SystemCorruptionException, так что если обработчик покемонов просто ловит, регистрирует и выбрасывает исключение, журнал будет отражать, что по крайней мере кто-то считает исключение важным).

Я считаю рекомендацию Microsoft избегать определения пользовательских типов исключений неудачной, поскольку это означает, что нет четкого способа различить, выдает ли IEnumerator<T>.MoveNext() InvalidOperationException, потому что базовая коллекция была изменена во время перечисления, или внутренняя обработка IEnumerator<T>.MoveNext() встречает InvalidOperationException и просто позволяет ему всплывать до вызывающего абонента. Если вместо этого IEnumerator<T>.MoveNext() бросил IEnumerator.InvalidatedEnumeratorException в первом случае, то любой InvalidOperationException, который ускользнул, должен представлять второй случай.

person supercat    schedule 02.02.2012
comment
Это похоже на то, что ищет OP, однако вы не говорите, как это сделать. - person Josh; 14.02.2017

Разве это не должно зависеть от того, какой класс реализует интерфейс?

Например, если я возьму ваш интерфейс и реализую свой собственный класс с помощью метода GetData, я смогу «получать» данные откуда угодно. Допустим, это была веб-служба, тогда типы исключений, которые могли бы быть выброшены, могли бы отличаться от тех, которые были бы, если бы я «получал» данные из локальной файловой системы.

Поэтому в своей реализации я бы реализовал (и задокументировал) эти исключения, характерные для реализации, чтобы любой код, использующий реализацию, мог их обрабатывать. То, как обрабатываются эти исключения, может быть очень специфичным для реализации, скажем, я использую их в веб-приложении, а не в настольном приложении.

person SleepyBoBos    schedule 02.02.2012
comment
Назначение интерфейса — обеспечить взаимозаменяемость различных реализаций. Если бы реализация IEnumerable/IEnumerator, которая считывает данные из AcmeDatabase, позволяла экранировать какой-либо тип AcmeDatabaseException, у вызывающего абонента не было бы возможности узнать, представляет ли это временный сбой связи или означает ли это, что процессор сгорел. Контракт IEnumerable, увы, не дает каких-либо полезных указаний, но это не причина, по которой другие интерфейсы не должны работать лучше, например. указав, что... - person supercat; 02.02.2012
comment
...любая попытка выполнить IWoozle.wozzle, которая не может быть завершена, но оставляет состояние системы неизменным, должна вызывать IWoozle.CleanFailureException или его производное. Если состояние IWoozle могло измениться, но, с точки зрения IWoozle, вероятно, верно, IWoozle.PossibleChangeException. Если состояние IWoozle, вероятно, повреждено, но повреждение ограничено этим объектом, IWoozle.CorruptObjectedException. Не допускайте выхода каких-либо исключений, кроме приведенных выше, если ЦП не горит или не существует других подобных серьезных условий. - person supercat; 02.02.2012
comment
Хорошо, я понимаю, что вы говорите, я думаю. Таким образом, в основном, если бы я взял интерфейс Gouldos и реализовал GetData, который за кулисами я закодировал для извлечения данных через веб-службу, и возникает какое-то конкретное исключение для веб-службы :-), тогда мне пришлось бы свернуть это исключение и передать любые детали вернуться через поднятие, скажем, NetworkConnectionException? И тогда вызывающая сторона будет полагаться на получение информации об основном исключении через свойство внутреннего исключения... что-то в этом роде? - person SleepyBoBos; 23.02.2012
comment
Именно так. Одна из причин использования интерфейсов состоит в том, чтобы позволить вызывающей стороне выполнять определенные действия с объектом, не беспокоясь о его деталях. Если какой-то код вызывает IDataRetriever.GetData и не может его получить, возможно, где-то есть человек, который будет заботиться о том, произошел ли сбой из-за ненайденного хоста DNS или недоступного хоста ICMP, но код непосредственного вызова может не иметь никакой подсказки. что объект пытается получить данные из Интернета и понятия не имеет, чем могут отличаться эти условия. - person supercat; 23.02.2012

Я думаю, что лучший способ предоставить эту информацию — XML-документация, которую вы предоставите для каждого интерфейса. Там вы можете указать, какое исключение вызывается методом, чтобы хост мог обработать эту ошибку.

person rauts    schedule 13.10.2010

Другая стратегия, если вы не можете поддерживать общий базовый класс, — это реализация метода расширения.

public static void ThisMethodShouldThrow(this Iinterface obj)
{
     if(obj.ConditionToThrowIsMet) throw new...
}

Преимущество этого заключается в том, что вам не требуется цепочка наследования.

person Michael B    schedule 13.10.2010