是否应该总是在.NET中编写null-proof扩展方法?

时间:2013-05-10 13:52:47

标签: c# .net extension-methods

想象一下,我们已经定义了一个采用这种形式的扩展方法:

public class Foo
{
    public void Bar(int arg) { ... }
}

public static class FooExtensions
{
     public static void Baz(this Foo @this)
     {
         @this.Bar(0);// not null-proof
     }
}

这里我们在Baz类上公开了一个公共扩展方法Foo(这个例子很简单)。现在,如果我们有以下用法:

Foo foo1 = null;
foo1.Bar(0); // throws NullReferenceException

Foo foo2 = null;
foo2.Baz(); // again throws NullReferenceException

因此,两种情况下的代码都会表现一致 - 无论调用成员方法还是扩展方法,我们都会得到相同的NullReferenceException()抛出。这让我觉得情况有些不妥。我的想法是:

  • 根据大多数指南,允许NullReferenceExceptions的代码很差。扩展方法就是这种不良做法的一个例子。为了遵守指南并将防止故障代码公开为公共API,必须进行一些类似的空安全检查:
    public static void Baz(this Foo @this)
    {
        if (@this == null) 
        {
            throw new ArgumentNullException("@this");
        }
        @this.Bar(0);
    }
  • 行为的一致性将允许开发人员轻松检测空引用情况,因为两种情况都表现相同。我的意思是,对编码人员来说,调用扩展方法并不总是显而易见的,所以如果行foo2.Baz()抛出一个NullReferenceExceptionfoo2显然是null

上述矛盾引出了一些结论。第二点错过了一个重要的问题 - 堆栈跟踪。在标准NullPointerException情况下,堆栈跟踪直接指向foo1.Bar(0)行。在扩展方法中,它将指向抛出异常的扩展方法中的行。因此,一致行为仍然存在不一致的堆栈跟踪。

现在问题 - 关于无效安全性,“最佳实践”如何适用于第三方将要使用的扩展方法?我们是否应该通过在@this参数上添加参数空值验证来忽略一致性?或者它是一个可以让我们绕过良好实践建议的角落案例?

修改

我正在解决一个带有扩展名的库将被暴露的情况。它不会使用非内置/第三方解决方案,如PostSharp或其他类似技术。还需要与.NET 3.5完全兼容。

2 个答案:

答案 0 :(得分:8)

这取决于您查看扩展方法的方式。鉴于它们仅仅是常规静态方法的语法糖,我会说它们应该遵循静态方法的指导原则 - 检查所有参数,包括this参数。

如果您有专门用于处理null的扩展程序,这一点尤其适用 - 我知道它不是大多数人使用扩展程序的首选方法,但我喜欢以下方法:

public static IEnumerable<T> EmptyIfNull<T>(this IEnumerable<T> source)
{
    return source ?? Enumerable.Empty<T>();
}

public static void DisposeIfNotNull(this IDisposable source)
{
    if (source != null)
        source.Dispose();
}

显然,必须允许参数null才能使这些方法起作用。

答案 1 :(得分:3)

当我在组织内遇到这样的问题时,我默认为常规。

任何对此有意见的人都知道他们在谈论什么。它只取决于它们是从实例还是静态方面查看扩展方法。

之前我使用过WWLD(LINQ会做什么?),因为它是一个使用大多数.NET开发人员习惯的扩展方法的公共库。

示例代码:

IEnumerable<int> test = null;
test.Where(t => t > 0); // throws an ArgumentNullException

所以不管我的意见是什么,我会使用ArgumentNullException,因为这是其他.NET开发人员习惯的。