Another way I saw recently:
public static int ThrowIfZero<T>(this T param)
where T : struct
{
var o = (object)param;
int i;
try { i = (int)o; }
catch { throw new ArgumentException("Param must be of type int"); }
if (i == 0) throw new ArgumentException("Must be not be zero");
return (int)(object)param;
}
We can trick the compiler into letting us cast T
into int
by casting it to object
first. This works fine for int
, but the downside is this only works for int
, and won't work for a float
.
If you want it to work with all numerable types, you can use pattern matching and do something like this:
public static T ThrowIfZero<T>(this T param)
where T : struct
{
switch (param)
{
case int i: if (i == 0) throwException(); break;
case double d: if (d == 0) throwException(); break;
case float f: if (f == 0) throwException(); break;
case decimal c: if (c == 0) throwException(); break;
case long l: if (l == 0) throwException(); break;
case uint ui: if (ui == 0) throwException(); break;
case ulong ul: if (ul == 0) throwException(); break;
case byte b: if (b == 0) throwException(); break;
default: throw new ArgumentException("Invalid Type");
}
return param;
// ---- Local Functions ---- //
void throwException() => throw new ArgumentException("Must not be zero");
}
Of course, the best solution would be if they took Jon Skeet up on his offer and did a where T : numeric
that constraints it to basically the types above, and maybe some custom types too, so we could include our own ComplexNumber
or SuperBigInteger
.
To be honest, I wouldn't do it either of these ways, because the first way has casting, and the second way just leads to missing cases and having to maintain the switch
if you need, just thought I'd show the more options, though.