Reflector告诉我,SortedList使用ThrowHelper类来抛出异常而不是直接抛出它们,例如:
public TValue this[TKey key]
{
get
{
int index = this.IndexOfKey(key);
if (index >= 0)
return this.values[index];
ThrowHelper.ThrowKeyNotFoundException();
return default(TValue);
}
其中ThrowKeyNotFoundException仅执行以下操作:
throw new KeyNotFoundException();
注意这需要一个duff语句“return default(TValue)”,这是无法访问的。我必须得出结论,这种模式的好处足以证明这一点。
这些好处是什么?
答案 0 :(得分:21)
根据ThrowHelper.cs源代码,主要目的是减少JITted代码大小。以下是链接中的直接复制粘贴:
// 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.
答案 1 :(得分:4)
看看ThrowHelper的作用。它获取错误消息的资源和内容。在这个特定的例子中,没有错误文本,所以看起来它没用,但是它们的模式可能需要它,所以编写它的开发人员遵循他/她应该的模式。
答案 2 :(得分:0)
另一个有趣的方面是性能。有趣的是,一个包含throw
语句的方法可能会变慢,即使未抛出异常也仅仅是因为JIT不愿意内联此类方法(也许是为了提高调用堆栈的可读性)。考虑以下示例:
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;
}
}
计算机上的性能结果:
(请参阅源链接以及下面的一些说明)
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%)
这意味着,带有显式throw
语句的方法要慢4.5倍!但是...
有趣的观察结果
:[MethodImpl(MethodImplOptions.AggressiveInlining)]
属性,尽管它不能保证任何保证。例如,在.NET中,Fiddle内联通常被禁用,因此两种方式实际上具有相同的性能。 See the source code and the completely different results here。ArgumentException
派生的异常类型才可以防止内联(不使用任何属性)。至少直接抛出NotSupportedException
或InvalidOperationException
不会对性能产生负面影响。冗余/无法访问的代码问题和代码分析器:
可以通过在return
中定义一些泛型重载来避免多余的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...
后者可以在return
语句中使用,可以在break
块中保留case
,从C#7.0开始,可以与throw表达式相同的方式使用:
return value >= 0 ? value + 1 : Throw.ArgumentOutOfRangeException<int>(Argument.value);
另一个问题是ReSharper和FxCop无法识别投掷助手成员,并可能开始发出错误的肯定警告。对于ReSharper,我们可以使用ContractAnnotation
属性:
// prevents PossibleNullReferenceException, AssignNullToNotNullAttribute and similar false alarms
[ContractAnnotation("=> halt")]
internal static void ArgumentException(Argument arg, string message) => throw new...
不幸的是,对于FxCop,我没有找到类似的解决方案(并且[DoesNotReturn]
属性显然不起作用),因此您应该使用#pragma warning disable
或SuppressMessage
属性来抑制CA1031,CA1062和他们的朋友。