扩展方法的ArgumentNullException或NullReferenceException?

时间:2009-01-20 21:56:43

标签: .net exception extension-methods

在null实例(扩展方法不允许)的情况下调用扩展方法时,您认为最好的异常类型是什么?由于扩展方法只不过是静态方法,你可以说它应该是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,因为在大多数方法中它似乎更自然使用

6 个答案:

答案 0 :(得分:36)

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

由于一些原因,抛出NullReferenceException是个坏主意

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

请参阅When can you catch a StackOverflowException(我就此主题发表的一篇文章)。

  • 调用扩展方法就像是常规方法一样,这是完全合法的。在那种情况下,除了NullReferenceException之外我肯定不会,而是ArgumentNullException。

答案 1 :(得分:25)

除了所有其他答案(这些都是好的)之外,我认为值得看一下Microsoft为了一致性所做的事情......并且Enumerable中的扩展方法都会抛出ArgumentNullException,就我所见。

答案 2 :(得分:7)

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

仅仅因为他们看起来类似于该类型的方法并不意味着他们是,或者总是被称为一个。

答案 3 :(得分:2)

ArgumentNullException。 no 要求调用扩展方法,就好像它们是实例方法一样。您可以将它们称为正常方法。在这种情况下,NullReferenceException完全不正确。

答案 4 :(得分:1)

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

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

答案 5 :(得分:0)

为了增加混乱,Microsoft会同时抛出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;
    }

此扩展方法从.NET Framework(System.Core / System / Linq / Enumerable.cs)中抛出ArgumentNullException

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,则抛出null。如果有人不恰当地调用我的扩展方法,他们可以自由地这样做,但也必须期待不适当的行为(NullReferenceException而不是ArgumentNullException)。但是,如果他们按预期方式调用该方法,则他们还应该获得预期的行为:始终如一的体验。

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

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

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