代码之家  ›  专栏  ›  技术社区  ›  Patrik Hägne

扩展方法中的ArgumentNullException或NullReferenceException?

  •  45
  • Patrik Hägne  · 技术社区  · 17 年前

    当在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 回复  |  直到 17 年前
        1
  •  39
  •   Pang Ajmal PraveeN    5 年前

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

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

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

    看见 When can you catch a StackOverflowException (我写的一篇关于这个主题的文章)。

    • 调用扩展方法就像调用常规方法一样是完全合法的。在这种情况下,我当然不会排除NullReferenceException,而是ArgumentNullException。
        2
  •  27
  •   Jon Skeet    17 年前

    除了所有其他答案(都很好),我认为微软为了保持一致性所做的工作值得一看。..和Enumerable中的扩展方法都抛出ArgumentNullException。

        3
  •  7
  •   casperOne    17 年前

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

    仅仅因为他们 类型上的like方法并不意味着它们是,或者总是被称为like方法。

        4
  •  3
  •   mqp    17 年前

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

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

        5
  •  2
  •   Craig Stuntz    17 年前

    ArgumentNullException。这是 要求调用扩展方法,就像它们是实例方法一样。你可以像调用普通方法一样调用它们。在这种情况下,NullReferenceException是完全不正确的。

        6
  •  0
  •   Christoph    6 年前

    更令人困惑的是,微软两者都做了,抛出 ArgumentNullExceptions 以及 NullReferenceExceptions .

    这个例子抛出了一个隐含 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框架(系统核心/系统/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);
    
    
       }
    

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

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

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

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

    推荐文章