如果查看 .net 框架代码的反编译源,大多数 API 都有类似的检查
if (source == null)
throw Error.ArgumentNull("source");
在方法参数上而不是使用更通用的类
Guard.IsNotNull(source);
每次都这样做是有原因的,还是这只是自框架开发以来一直存在的遗留代码,并且较新的类正在朝着这个方向发展,或者进行显式检查是否有任何固有的优势?我可以考虑的一个原因可能是避免使用函数指针使堆栈重载。
添加到马修斯的答案:
您建议的语法Guard.IsNotNull(source);
不直接等同于第一个代码片段。它只传递参数的值而不传递其名称,因此抛出的异常无法报告违规参数的名称。它只知道参数之一是null
.
您可以使用表达式树 - 像这样: - 但是分析这个表达式树在运行时Guard.IsNotNull(() => source);
对性能有相当大的影响,所以这也不是一个选项。
您建议的语法只能与静态编织器结合使用。这基本上是一个更改生成的 IL 的后编译器。这就是代码合同正在使用的方法。但这有其自身的成本,即:
现在我们可以这样做,Code Contracts
所以我们可以使用:
Contract.Requires(source != null);
Contract.Ensures(Contract.Result<MyType>() != null);
等等,但目前我们只能在我们自己的代码中执行此操作,因为它还没有内置到 CLR 中(它是一个单独的下载)。
Code Contracts 类本身从版本 4 开始就是 .Net 的一部分,但它们本身不会生成任何检查代码。为此,我们需要 C# 编译器在生成代码时调用的代码契约重写器。那是需要单独下载的东西。
所以是的,我们现在有更好的方法来做到这一点,但是它还没有作为 CLR 的一部分发布(还),所以 CLR 目前正在使用你认为的“遗留”方法。
这当然与“用函数指针重载堆栈”无关。
即使使用代码合同,我们当然仍在进行检查。据我所知,没有任何 IL 命令可以检查参数是否为 null,如果是则抛出,因此必须使用多个 IL 指令(在所有 CLR 语言中)来完成此类工作。但是,Code Contracts 代码重写器确实会生成内联代码来检查 Code Contract 的谓词(例如value != null
),而不是调用方法来执行此操作,因此非常有效。
.NET 框架中没有 Guard 类,因此您提出的替代方案不可行。后来对框架的添加确实使用了代码合同,但相当谨慎。并非微软的每个 .NET 程序员似乎都相信合同很有用,我确实同意这种观点。
否则,您会看到 Microsoft 的工作方式。.NET 框架中的代码由公司内的许多小团队贡献。典型的团队规模约为 10 名程序员。否则,软件开发行业的每个人都知道,大团队是行不通的。有一个临界质量,花在让每个人交流上的时间开始超过可以花在实际生成代码上的时间。
这样的团队也在不断地创建和解散。框架的许多部分不再有一个活跃的团队来维护它。通常只有一个人仍然足够了解内部结构以提供关键的安全更新,并且可能在必要时修复错误。这样一个解散的团队编写的代码非常处于维护模式,只有在绝对必要时才会进行更改。不仅因为进行细微的样式更改没有任何好处,而且还可以减少在不知不觉中添加重大更改的可能性。
这是 .NET 框架的一个责任,有很多内部组件具有使外部可见的诀窍,即使该代码位于私有方法中。像例外。程序员使用反射来破解框架限制。真正微妙的东西,一个很好的例子是微软内部广泛使用的电子邮件应用程序中的错误,由一名实习生编写。当他们将机器从 .NET 1.1 更新到 .NET 2.0 时,它崩溃了并且让每个人都没有电子邮件。该电子邮件应用程序中的错误是使用 .NET 1.1 运行时从未触发的潜在线程竞争。但是通过 .NET 2.0 框架代码的时间上的微小变化变得可见。
它可能不是 .NET Framework 的一部分,但 Microsoft 开发人员似乎接受了这个概念(注意使用 JetBrains 注释而不是代码合同):
https://github.com/aspnet/EntityFramework/blob/master/src/Shared/Check.cs
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using JetBrains.Annotations;
namespace Microsoft.Data.Entity.Utilities
{
[DebuggerStepThrough]
internal static class Check
{
[ContractAnnotation("value:null => halt")]
public static T NotNull<T>([NoEnumeration] T value, [InvokerParameterName] [NotNull] string parameterName)
{
NotEmpty(parameterName, "parameterName");
if (ReferenceEquals(value, null))
{
throw new ArgumentNullException(parameterName);
}
return value;
}
...
我唯一能想到的是,如果你有一个Guard
类,那么在异常堆栈跟踪中,它看起来好像问题出在Guard
实际调用的方法中Guard
。你可以通过捕获和重新抛出来解决这个问题,但是你的生产代码中又出现了样板。