18

I'm currently developing a solution and have designed it in a way such that it strongly implements the strategy/provider pattern. As such the solution exposes a number of interfaces and contains default implementations of these interfaces which can be replaced via a DI type methodology.

Where the host application uses a number of these interfaces it is expecting to handle certain exceptions that may occur, for example IDataRetriever interface has a method SomeDataType GetData(int timeout);and the host can handle some custom exceptions such as DataRetrievalTimeoutException or NetworkConnectionException.

My question is, what is the best way to mark up the interface class such that when a developer implements it they would know that certain exceptions should be thrown and would be handled by the host?

At the moment I have just added the exception xml tags to the methods xml comment - does this suffice?

Luke Girvin
  • 13,221
  • 9
  • 64
  • 84
gouldos
  • 1,015
  • 1
  • 16
  • 30

6 Answers6

15

The XML tags (and any other documentation you want to write) are basically the closest you've got in "vanilla" .NET at the moment.

You might want to look at Code Contracts which lets you annotate your interface with contracts, which can include exceptions, preconditions etc.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
3

You cannot in an interface. You CAN in a base class.

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);
}
  • 1
    If you want to ensure that `Bar` doesn't leak any unexpected exception types, I think the non-virtual `Bar` method needs to wrap the call to `InnerBar` in a try-catch block, which should catch and wrap any exceptions other than those specified in the interface. – supercat Feb 02 '12 at 20:05
3

I would suggest defining exception classes within the interface, and specifying that no exceptions which do not derive from those should be allowed to escape unless the CPU is on fire or other such drastic situation exists (even then, it might not be a bad idea to have an explicitly-defined IWoozle.SystemCorruptionException so that if a Pokemon handler simply catches, logs, and throws out the exception, the log will reflect that at least someone thought the exception was important).

I consider Microsoft's recommendation to avoid defining custom exception types to be unfortunate, since it means that there's no clean way to distinguish whether IEnumerator<T>.MoveNext() throws an InvalidOperationException because the underlying collection was altered during enumeration, or whether the internal processing of IEnumerator<T>.MoveNext() encounters an InvalidOperationException and simply lets it bubble up to the caller. If instead, IEnumerator<T>.MoveNext() threw an IEnumerator.InvalidatedEnumeratorException in the former case, then any InvalidOperationException which escaped would have to represent the latter case.

supercat
  • 77,689
  • 9
  • 166
  • 211
  • 1
    This sounds like what the OP is looking for, however you don't say *how* to do it. – Josh Feb 14 '17 at 12:46
  • I guess that when he said don't use no specified exceptions it means in documentation, warning to whom wants to implement the interface, has to use only allowed exceptions, but nothing avoids it could use another exceptions. So there is not way with annotations or another way to block the use the not allowed exceptions. It would be better perhaps to code, but it is only to create a custom exception, create the interface and comment in the interface for example that only should be used the custom exceptions. – Álvaro García Feb 22 '23 at 16:14
  • @ÁlvaroGarcía: I've not really kept up with changes to .NET and Java over the nears, but I think both frameworks failed to adequately capture the distinction between three scenarios: (1) a wrapped function throws an exception that mid-level code knows nothing about, in a situation where the mid-level code doesn't mind skipping the balance of its code); (2) a wrapped function throws an ETMLCKNA in a situation where the mid-level code is relying upon the called function to return normally; (3) the mid-level code detects a condition which it is responsible for reporting. – supercat Feb 22 '23 at 17:36
1

I think the best way to provide this information is in the XML documentation that you would provide for each interface. There you can specify what Exception is thrown by the method so that the host can handle that error.

rauts
  • 1,018
  • 9
  • 21
1

Should this not be up to whatever class implements the Interface?

Eg if I take your Interface and implement my own class, with a method GetData, I could be 'getting' data from anywhere. Lets say it was a web service then the types of exceptions that could be thrown could be different to those if I was 'getting' data from local file system.

So in my implementation I would implement (and document) those exceptions specific to the implmentation so that whatever code is using the implementation could handle them. How those exception are handled may be very specific to the implementation, say I am consuming them in a web app as opposed to a desktop app.

SleepyBoBos
  • 1,411
  • 2
  • 11
  • 16
  • 1
    The purpose of an interface is to allow different implementations to be interchangeable. If an implementation of `IEnumerable/IEnumerator` which read data from an `AcmeDatabase` were to allow some type of `AcmeDatabaseException` to escape, a caller would have no way of knowing whether that represented a transient communications hiccup, or whether it meant that the CPU was on fire. The `IEnumerable` contract fails to supply any useful guidance, alas, but that's no reason other interfaces shouldn't do better, e.g. specifying that... – supercat Feb 02 '12 at 16:39
  • 1
    ...any attempt to perform of `IWoozle.wozzle` which is unable to complete but leaves the system state unaltered should throw `IWoozle.CleanFailureException` or a derivative thereof. If the state of the `IWoozle` may have changed but is, from the `IWoozle`'s perspective likely valid, `IWoozle.PossibleChangeException`. If the state of the `IWoozle` is likely corrupt, but corruption is confined to that object, `IWoozle.CorruptObjectedException`. Do not allow any exception other than the above to escape unless the CPU is on fire or other such drastic condition exists. – supercat Feb 02 '12 at 16:45
  • Ok, I get what your saying I think. So basically if I took Gouldos interface and implemented GetData, which behind the scenes I have coded to retrieve data via a web service, and some web service specific exception occurs :-), then I would have to roll this exception up and pass any details back through the raising of say the NetworkConnectionException? And the caller would then rely on retrieving information about the underlying exception via the inner exception property....something like that? – SleepyBoBos Feb 23 '12 at 07:15
  • Precisely. One of the reasons for using interfaces is to allow a caller to do certain things with an object without having to worry about its particulars. If some code calls `IDataRetriever.GetData` and it can't get it, there may be some human somewhere who would care about whether it failed because of a "DNS Host Not Found", or an "ICMP Host Unreachable", but the immediate calling code may not have any clue that the object is trying to fetch data from the Internet, and would have no concept of how any of those conditions might differ. – supercat Feb 23 '12 at 15:54
0

Another strategy if you can't support a generic base class, is extension method implementations.

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

This has the benefit of allowing you to not require an inheritance chain.

Michael B
  • 7,512
  • 3
  • 31
  • 57