Reflector tells me that SortedList uses a ThrowHelper class to throw exceptions instead of throwing them directly, for example:
public TValue this[TKey key]
{
get
{
int index = this.IndexOfKey(key);
if (index >= 0)
return this.values[index];
ThrowHelper.ThrowKeyNotFoundException();
return default(TValue);
}
where ThrowKeyNotFoundException does nothing more than just:
throw new KeyNotFoundException();
Note how this requires a duff statement "return default(TValue)" which is unreachable. I must conclude that this is a pattern with benefits large enough to justify this.
What are these benefits?
According to ThrowHelper.cs source code the main purpose is to reduce the JITted code size. Below is a direct copy paste from the link:
// This file defines an internal class used to throw exceptions in BCL code.
// The main purpose is to reduce code size.
//
// The old way to throw an exception generates quite a lot IL code and assembly code.
// Following is an example:
// C# source
// throw new ArgumentNullException("key", Environment.GetResourceString("ArgumentNull_Key"));
// IL code:
// IL_0003: ldstr "key"
// IL_0008: ldstr "ArgumentNull_Key"
// IL_000d: call string System.Environment::GetResourceString(string)
// IL_0012: newobj instance void System.ArgumentNullException::.ctor(string,string)
// IL_0017: throw
// which is 21bytes in IL.
//
// So we want to get rid of the ldstr and call to Environment.GetResource in IL.
// In order to do that, I created two enums: ExceptionResource, ExceptionArgument to represent the
// argument name and resource name in a small integer. The source code will be changed to
// ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key, ExceptionResource.ArgumentNull_Key);
//
// The IL code will be 7 bytes.
// IL_0008: ldc.i4.4
// IL_0009: ldc.i4.4
// IL_000a: call void System.ThrowHelper::ThrowArgumentNullException(valuetype System.ExceptionArgument)
// IL_000f: ldarg.0
//
// This will also reduce the Jitted code size a lot.
Look at what ThrowHelper does. It gets resources and stuff for the error messages. In this particular instance, there's no error text, so it seems like it's useless, but their pattern probably requires it, so the developer who wrote it followed the pattern like s/he should.
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:
[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.ArgumentException
prevented inlining (without using any attributes). At least throwing a NotSupportedException
or InvalidOperationException
directly did not affect the performance negatively.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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With