43

当在空实例上调用扩展方法时(扩展方法不允许),您认为最好的异常类型是什么?由于扩展方法只不过是静态方法,您可以争辩说它应该是 ArgumentNullException,但另一方面,它们像实例方法一样使用,因此使用 NullReferenceException 可能更自然。让我们看下面的例子:

public static string ToInvariantString(this IFormattable value, string format)
{
    return value.ToString(format, CultureInfo.InvariantCulture);
}

这样,如果 value 参数为 null,则会抛出 NullReferenceException。

另一个例子是:

public static string ToInvariantString(this IFormattable value, string format)
{
    if (value == null) throw new ArgumentNullException("value");
    return value.ToString(format, CultureInfo.InvariantCulture);
}

编辑: 在您指出的一些答案中,可以像静态方法一样调用扩展方法,在这种情况下,空引用异常是错误的,这是一个很好的观点,实际上是我的担忧之一,不知道为什么我忘了首先在问题中提到这一点。

也有人指出,抛出 NullReferenceException 是错误的,没错,就是这样。这就是我不扔它的原因,我只是让它发生(让 CLR 扔它),而不是保护方法。

我认为我赞成 ArgumentNullException (这是我迄今为止使用的),但我仍然认为至少有反对 NullReferenceException 的空间,因为它在大多数将要使用该方法的地方看起来更自然。

4

6 回答 6

39

通常,包括异常在内,您应该将扩展方法视为普通的静态方法。在这种情况下,您应该抛出 ArgumentNullException。

由于几个原因,在这里抛出 NullReferenceException 是一个坏主意

  • 空引用实际上并没有发生,所以看到一个是违反直觉的
  • 抛出 NullReferenceException 并导致 NullReferenceException 发生会产生明显不同的异常(查看差异的一种方法是错误代码)。CLR 引发的许多异常都是如此。

请参阅何时可以捕获 StackOverflowException(我在此主题上发表的一篇文章)。

  • 像调用常规方法一样调用扩展方法是完全合法的。在那种情况下,我当然不会排除 NullReferenceException,而是 ArgumentNullException。
于 2009-01-20T22:06:46.320 回答
27

除了所有其他答案(很好)之外,我认为值得看看微软为了一致性所做的事情......据我所知,Enumerable 中的扩展方法都会抛出 ArgumentNullException 。

于 2009-01-20T22:52:40.270 回答
7

由于可以在 C# 2.0 中使用扩展方法,并且可以像调用静态方法一样调用它们(您不必将它们用作扩展方法),因此您应该使用 ArgumentNullException。

仅仅因为它们看起来像类型上的方法并不意味着它们是,或者总是被称为一个。

于 2009-01-20T22:01:24.413 回答
3

从用户的角度来看,该方法看起来和行为都像一个实例方法,所以如果我是他们,我希望看到 NullReferenceException。

也就是说,我建议在代码中明确地抛出一个或另一个,而不是像第一个示例中那样“发生”抛出一个。

于 2009-01-20T21:59:03.230 回答
2

参数空异常。不需要像调用实例方法一样调用扩展方法。您可以像调用普通方法一样调用它们。在这种情况下,NullReferenceException 将是完全不正确的。

于 2009-01-20T22:01:01.043 回答
0

更令人困惑的是,微软两者都做,投掷ArgumentNullExceptionsNullReferenceExceptions.

这个引发隐含NullReferenceException的示例来自 Roslyn (src\Workspaces\CSharp\Portable\Extensions\StringExtensions.cs):

internal static class StringExtensions
{
    public static string EscapeIdentifier(
        this string identifier,
        bool isQueryContext = false)
    {
        var nullIndex = identifier.IndexOf('\0');
        if (nullIndex >= 0)
        {
            identifier = identifier.Substring(0, nullIndex);
        }

        var needsEscaping = SyntaxFacts.GetKeywordKind(identifier) != SyntaxKind.None;

        // Check if we need to escape this contextual keyword
        needsEscaping = needsEscaping || (isQueryContext && SyntaxFacts.IsQueryContextualKeyword(SyntaxFacts.GetContextualKeywordKind(identifier)));

        return needsEscaping ? "@" + identifier : identifier;
    }

这个ArgumentNullException从 .NET Framework (System.Core/System/Linq/Enumerable.cs) 抛出的扩展方法:

public static IEnumerable<TResult> Select<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector) {
            if (source == null) throw Error.ArgumentNull("source");
            if (selector == null) throw Error.ArgumentNull("selector");
            if (source is Iterator<TSource>) return ((Iterator<TSource>)source).Select(selector);
            if (source is TSource[]) return new WhereSelectArrayIterator<TSource, TResult>((TSource[])source, null, selector);
            if (source is List<TSource>) return new WhereSelectListIterator<TSource, TResult>((List<TSource>)source, null, selector);
            return new WhereSelectEnumerableIterator<TSource, TResult>(source, null, selector);


   }

正如上面评论中提到的,我建议将扩展方法完全实现为实例方法,因此NullReferenceException如果this-parameter 为,则抛出 a null。如果有人不恰当地调用我的扩展方法,他们可以自由地这样做,但也必须期待不恰当的行为(aNullReferenceException而不是 an ArgumentNullException)。但是,如果他们按预期调用该方法,他们也应该得到预期的行为:完全一致的体验。

//Instance method
string foo = null;
foo.Trim();

//Extension method
string foo = null;
foo.Right(10); 

它们看起来相似,行为也应该相似,程序员甚至不需要知道它是实例还是扩展方法。

于 2020-02-28T14:03:40.930 回答