另一个有趣的方面是性能。有趣的是,包含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 倍!但是……
有趣的观察:
- 从 .NET 4.0 开始,您可以应用
[MethodImpl(MethodImplOptions.AggressiveInlining)] 属性,但它不能保证任何事情。例如,在 .NET Fiddle 中,内联似乎通常被禁用,因此两种方式实际上具有相同的性能。 See the source code and the completely different results here。
- 在 .NET Core 3(Windows 10 上的 x64 版本)中,似乎只有从
ArgumentException 派生的异常类型才能阻止内联(不使用任何属性)。至少直接抛出NotSupportedException 或InvalidOperationException 不会对性能产生负面影响。
- 对于更复杂的方法,throw helper 无论如何都没有任何意义。但它对短的性能关键成员很有用。
冗余/无法访问的代码问题和代码分析器:
可以通过在ThrowHelper 中定义一些通用重载来避免多余的return 语句:
// 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 语句中,可以在case 块中备用break,从C# 7.0 开始,它可以像throw 表达式一样使用:
return value >= 0 ? value + 1 : Throw.ArgumentOutOfRangeException<int>(Argument.value);
另一个问题是 ReSharper 和 FxCop 无法识别 throw helper 成员并可能开始发出误报警告。对于 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 和他们的朋友。