Skip to content

Exception handling helpers #1462

@lipchev

Description

@lipchev

There are currently two types extending the UnitsNetException (AmbigiousUnitParseException and the UnitNotFoundException) - with a bunch of standard exceptions that are scattered around (a lot of them part of the auto-generated code).

Besides the likely inconsistency in the messaging, having the same string format repeated in code is of course also increasing our file size.

Here is the list of exceptions that I think should cover all of our cases:

  • UnitNotFoundException: this one is pretty obvious, it is thrown by the UnitConverter / UnitParser etc - it is also the exception that I typically use when given an invalid unit, such as (MassUnit)(-1)
  • QuantityNotFoundException: this is thrown when given a type (or type name) that is unknown- this one is thrown by the QuantityInfoLookup (which is itself called by the Quantity.Parse or the UnitConverter)
  • InvalidConversionException: I'm not sure if this one applies to the current state of the v6 , but in my new implementation of the UnitConverter this is thrown when attempting to convert one quantity to another, incompatible, quantity type
  • AmbiguousUnitParseException: thrown by the UnitParser

In addition, I would create a new solution folder called Exceptions (no namespace generation), with all of the above classed inside it (personally, I think it kinda sucks to have all classes in one namespace, but I don't think we can easily move away from that..).

Finally, for the more typical exception-throwing scenarios, I'd like to add some static helper methods. Here's what I've come up so far:

internal static class ExceptionHelper
{
    internal static ArgumentException CreateArgumentException<TUnit>(Enum unit, string argumentName)
        where TUnit : struct, Enum
    {
        return new ArgumentException($"The given unit is of type {unit.GetType()}. The expected type is {typeof(TUnit)}.", argumentName);
    }

    internal static ArgumentException CreateArgumentException<TQuantity>(object obj, string argumentName)
        where TQuantity : IQuantity
    {
        return new ArgumentException($"The given object is of type {obj.GetType()}. The expected type is {typeof(TQuantity)}.", argumentName);
    }

    internal static ArgumentException CreateArgumentOutOfRangeExceptionForNegativeTolerance(string argumentName)
    {
        return new ArgumentOutOfRangeException(argumentName, "The tolerance must be greater than or equal to 0");
    }

    internal static InvalidOperationException CreateInvalidOperationOnEmptyCollectionException()
    {
        return new InvalidOperationException("Sequence contains no elements");
    }
    
    internal static InvalidCastException CreateInvalidCastException<TQuantity, TOther>()
        where TQuantity : IQuantity
    {
        return CreateInvalidCastException<TQuantity>(typeof(TOther));
    }
    
    internal static InvalidCastException CreateInvalidCastException<TQuantity>(Type targetType)
        where TQuantity : IQuantity
    {
        return new InvalidCastException($"Converting {typeof(TQuantity)} to {targetType} is not supported.");
    }
}

We could probably move these strings to a resources dictionary, but I'm not too concerned with this ATM.

What I'm not sure about is whether the helpers should return or throw the exception.. If I'm not mistaken the second option, while a bit more convenient, would introduce an additional row in the stack trace compared to the first one. I know of the [DoesNotReturn] attribute but I don't think it affects the stack trace. What do you think?

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions