4

I'm working on a factory which shall return a generic implementation of an interface according to a type.

My main question is being illustrated by
enter image description here Why are these typeof(TException) != exception.GetType()? Respectively, what do I have to change to have the correct type for TException?

The above code results in a InvalidCast exception because it tries to cast to IDocumedisExceptionHandler<DocumedisException> instead of IDocumedisExceptionHandler<FhirParsingException>

Factory implementation:

internal class DocumedisExceptionHandlerFactory : IDocumedisExceptionHandlerFactory
{
    private readonly IDictionary<Type, object> _exceptionHandlers = new ConcurrentDictionary<Type, object>();

    public void RegisterExceptionHandler<TException>(IDocumedisExceptionHandler<TException> exceptionHandler)
        where TException : DocumedisException
    {
        _exceptionHandlers.Add(typeof(TException), exceptionHandler);
    }

    public IDocumedisExceptionHandler<TException> GetDocumedisExceptionHandler<TException>(TException exception)
        where TException : DocumedisException
    {
        _exceptionHandlers.TryGetValue(exception.GetType(), out var exceptionHandler);
        return (IDocumedisExceptionHandler<TException>) exceptionHandler;
    }
}

Side question: Would there be a better way then using object as dictionary value?

Registration of the handler in startup:

var exceptionHandlerFactory = app.ApplicationServices.GetService<IDocumedisExceptionHandlerFactory>();
exceptionHandlerFactory.RegisterExceptionHandler(new FhirParsingExceptionHandler());

Where FhirParsingExceptionHandler implements IDocumedisExceptionHandler

internal class FhirParsingExceptionHandler : IDocumedisExceptionHandler<FhirParsingException>
{
    public void HandleException(FhirParsingException exception, out HttpStatusCode httpStatusCode, out OperationOutcome.IssueType issueType, out string message)
    {
        httpStatusCode = HttpStatusCode.BadRequest;
        issueType = OperationOutcome.IssueType.Invalid;
        message = exception.Message;
    }
}

Handler definition (where TException is contravariant):

public interface IDocumedisExceptionHandler<in TException>
    where TException : DocumedisException
{
    void HandleException(TException exception, out HttpStatusCode httpStatusCode, out OperationOutcome.IssueType issueType, out string message);
}

And FhirParsingException extends DocumedisException:

public class FhirParsingException : DocumedisException
{
   [...]
}

Retrieval of the handler from the middleware:

public async Task Invoke(HttpContext context)
{
   try
   {
      await _next.Invoke(context);
   }
   catch (Exception ex)
   {
      if (ex is DocumedisException documedisException)
      {
         await HandleDocumedisExceptionAsync(context, documedisException);
      }
      else
      {
         throw;
      }
   }
}

private async Task HandleDocumedisExceptionAsync<TException>(HttpContext context, TException ex, MedicationAnalyzerErrorCode? errorCode = null)
   where TException : DocumedisException
{
   var exceptionHandler = _documedisExceptionHandlerFactory.GetDocumedisExceptionHandler(ex);
   [...]
}
Philippe
  • 1,949
  • 4
  • 31
  • 57
  • Does this answer your question? [Type Checking: typeof, GetType, or is?](https://stackoverflow.com/questions/983030/type-checking-typeof-gettype-or-is) – Pavel Anikhouski Apr 08 '20 at 14:35
  • @PavelAnikhouski Not really. It explains the differences, but not how I can get TException of the correct type. As explained by InBetween, it is determined at compile time. – Philippe Apr 08 '20 at 14:46
  • How is `IDocumedisExceptionHandler` declared? Is covariant against `TException` or not? It seems, that your question is related to generics mostly – Pavel Anikhouski Apr 08 '20 at 14:56
  • @PavelAnikhouski It is contravariant (only because resharper told me to do so). I've edited the question to include the interface – Philippe Apr 08 '20 at 15:04
  • Then you cast is not valid, it'll work in case of covariant generic interface. You should spend some time to refine your current approach and code – Pavel Anikhouski Apr 08 '20 at 15:45

1 Answers1

5

typeof(TException) gives you the compile time type of exception. exception.GetType() gives you the runtime type of exception. These two need not be the same at all, the only guarantee the compiler makes is that the runtime type of exception will be assignable to a TException variable.

Consider the following:

class Animal { }
class Turtle: Animal { }
bool CheckTypes<T>(T animal) where T: Animal 
{
     return typeof(T) == animal.GetType();
}

And now you have:

Animal animal = new Turtle();
Feed(animal);

Rest assured, CheckTypes will return false; the type of the generic type argument is Animal but the runtime type of animal is really Turtle.

InBetween
  • 32,319
  • 3
  • 50
  • 90
  • This answers the main question of the why, thanks for that! But I don't see a way to solve my issue to retrieve a typed handler, expecting an exact exception type, from the factory. I could pass DocumedisException and cast it in the handler but would really like to avoid that, – Philippe Apr 08 '20 at 14:44
  • @Philipple Show the `IDocumedisExceptionHandler` interface. If you can make it covariant (`IDocumedisExceptionHandler`) then the cast should succeed; same as `IEnumerable` can be cast to `IEnumerable`. If its contravariant or invariant then you are stuck in this current set up. – InBetween Apr 08 '20 at 15:00
  • I've added the interface definition as Pavel also requested. I'll have to do some readining about co- and contra-variant.. – Philippe Apr 08 '20 at 15:08
  • @Philippe The interface is contravariant, therefore the cast you are attempting is not valid. – InBetween Apr 08 '20 at 15:35
  • I couldn't figure out a way to make it covariant in the time I had at my disposal. I made it non-generic by passing DocumedisException to the handler. The handler can then cast it if required. Not perfect, but fully functional. – Philippe Apr 09 '20 at 11:14
  • @philippe good decision. Sometimes when the type system is fighting against you, the best solution is to take a step back and approach the problem from a different angle. Generics is very useful but its not always the best tool for every job. – InBetween Apr 09 '20 at 12:12