Another interesting aspect is performance. Interestingly enough, a method, which contains a throw
statement can be slower even if the exception is not thrown just because JIT prefers not to inline such methods (maybe for the better readability of the call stack). Consider the following example:
private class TestClass
{
internal int RegularThrow(int value)
{
if (value < 0)
throw new ArgumentOutOfRangeException(nameof(value));
return value + 1;
}
internal int ThrowByHelper(int value)
{
if (value < 0)
Throw.ArgumentOutOfRangeException(Argument.value); // Argument is an enum
return value + 1;
}
}
Performance results on my computer:
(See the source link along with some remarks below)
1. ThrowByHelper: average time: 5,24 ms
#1 5,26 ms
#2 5,16 ms <---- Best
#3 5,31 ms <---- Worst
Worst-Best difference: 0,16 ms (3,02%)
2. RegularThrow: average time: 23,51 ms (+18,27 ms / 448,40%)
#1 23,46 ms
#2 23,42 ms <---- Best
#3 23,65 ms <---- Worst
Worst-Best difference: 0,22 ms (0,95%)
Meaning, the method with the explicit throw
statement was 4.5 times slower! But...
Interesting Observations:
- Starting with .NET 4.0 you can apply the
[MethodImpl(MethodImplOptions.AggressiveInlining)]
attribute, though it does not guarantee anything. For example, in .NET Fiddle inlining appears to be generally disabled, hence both ways have effectively the same performance. See the source code and the completely different results here.
- In .NET Core 3 (x64 release build on Windows 10) it seems that only exception types derived from
ArgumentException
prevented inlining (without using any attributes). At least throwing a NotSupportedException
or InvalidOperationException
directly did not affect the performance negatively.
- For more complex methods a throw helper does not make any sense anyway. But it can be useful in short performance-critical members.
Redundant/unreachable code issue and code analyzers:
The redundant return
statement can be avoided by defining some generic overloads in the ThrowHelper
:
// for regular usage:
internal static void ArgumentException(Argument arg, string message) => throw new...
// for expression usage:
internal static T ArgumentException<T>(Argument arg, string message) => throw new...
The latter can be used in a return
statement, can spare a break
in case
blocks and starting with C# 7.0 it can be used the same way as throw expressions:
return value >= 0 ? value + 1 : Throw.ArgumentOutOfRangeException<int>(Argument.value);
Another issue is that ReSharper and FxCop do not recognize the throw helper members and may start emitting false positive warnings. For ReSharper we can use the ContractAnnotation
attribute:
// prevents PossibleNullReferenceException, AssignNullToNotNullAttribute and similar false alarms
[ContractAnnotation("=> halt")]
internal static void ArgumentException(Argument arg, string message) => throw new...
Unfortunately for FxCop I did not find a similar solution (and the [DoesNotReturn]
attribute apparently does not work) so you should use #pragma warning disable
or the SuppressMessage
attribute to suppress CA1031, CA1062 and their friends.