2

I have an implementation of this answer in some code of mine, as:

private interface IMath<T> {
    internal T Add (T value1, T value2);
    internal T Negate (T value);
}

private class Math<T> : IMath<T> {
    internal static readonly IMath<T> P = Math.P as IMath<T> ?? new Math<T>();
    // !!! My question concerns this portion of code:
    T IMath<T>.Add (T a, T b) { NoSupport(); }
    T IMath<T>.Negate (T a) { NoSupport(); }
    private static void NoSupport () =>
        throw new NotSupportedException($"no math ops for {typeof(T).Name}");
    // !!! End code-of-interest.
}

private class Math : IMath<int>, IMath<float> {
    internal static Math P = new Math();
    int IMath<int>.Add (int a, int b) { return a + b; }
    int IMath<int>.Negate (int value) { return -value; }
    float IMath<float>.Add (float a, float b) { return a + b; }
    float IMath<float>.Negate (float value) { return -value; }
}

Where the intent is e.g.:

static T Negate <T> (T v) => Math<T>.P.Negate(v);

// elsewhere...
_ = Negate(3);    // ok (int)
_ = Negate(3.0f); // ok (float) 
_ = Negate(3.0);  // throws NotSupportedException (double)

That NoSupport() function is what I am having a problem with. I just added it to take care of throwing an exception and a message for unsupported types, to try to keep the code simple as I add more operations.

However, it fails to compile (C# 8) with the expected "not all control paths return a value" error in the two methods (Add and Negate) that call it.

I understand that, and I understand why it's not compiling, and that makes sense. But, then, how can I accomplish my goal of keeping the code simple and convenient here while also satisfying the compiler?

From the research I've done so far, it appears there isn't a way to specify that a method specifically doesn't return, but I'm wondering if there's maybe a way to ...

  • ... specify that NoSupport() always throws an exception? Or ...
  • ... mark a line of code as unreachable (e.g. after the call to NoSupport())? Or ...
  • ... mark a method as always actually returning a value (overriding the compiler's analysis)?

My primary goal is to eliminate redundant code (i.e. I'm open to other approaches), my secondary goal is to learn about C#'s specific options for dealing with methods where all paths do return a value even if the compiler can't see it (are there any options?).

I have this feeling that there is a very straightforward approach and that I just can't see the forest for the trees right now.

Jason C
  • 38,729
  • 14
  • 126
  • 182
  • 2
    I guess you could make `NoSupport()` return `T` and in the method itself return `default(T)`. Would that be simple enough? – Jack T. Spades May 30 '21 at 21:24
  • @JackT.Spades Oh! Wow that's so obvious. Yeah that's exactly what I'm going to do then and, even better, I don't actually have to return anything from `NoSupport()` since the compiler doesn't complain with the `throw` there. So e.g.: `T NoSupport () => throw ...;` and each method simply becomes `T Add(...) => NoSupport();`. Thank you!! You can post an answer if you want (but be warned: it might be collateral damage if my question gets dv'd to heck, heh). – Jason C May 30 '21 at 21:27

1 Answers1

3

Most trivial solution would be to just have NoSupport() change from void to T.

Thus becoming:

    T IMath<T>.Add (T a, T b) => NoSupport();
    T IMath<T>.Negate (T a) => NoSupport();
    private static T NoSupport () =>
        throw new NotSupportedException($"no math ops for {typeof(T).Name}");

Additionally, you could also spice your error message up a bit with CallerMemberNameAttribute. Which will automatically include the name of the method which called NotSupport.

    T IMath<T>.Add (T a, T b) => NoSupport(); //will throw "Add not supported"
    T IMath<T>.Negate (T a) => NoSupport();   //will throw "Negate not supported"
    private static T NoSupport ([CallerMemberName] string method = "") =>
        throw new NotSupportedException($"{method} not supported.");
Jack T. Spades
  • 986
  • 6
  • 9
  • 1
    Excellent, [worked like a charm](https://dotnetfiddle.net/5fFi6X) and super straightforward (I really XY'd the heck out of my attempts, heh), thanks again!! – Jason C May 30 '21 at 21:31
  • 1
    (Ooo, re: your edit, yeah I'm definitely going to put some seasoning on that error message, good call.) – Jason C May 30 '21 at 21:36