6

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

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
     }
}

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

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)
        {
            抛出新的 ArgumentNullException("@this");
        }
        @this.Bar(0);
    }
  • 行为的一致性将允许开发人员轻松检测空引用情况,因为两种情况的行为相同。我的意思是,调用扩展方法对编码人员来说并不总是显而易见的,所以如果该行foo2.Baz()抛出 aNullReferenceException很明显foo2就是null.

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

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

编辑

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

4

2 回答 2

9

当我在我的组织内遇到这样的问题时,我默认使用约定。

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

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

示例代码:

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

因此,无论我的意见是什么,我都会使用ArgumentNullException其他 .NET 开发人员会习惯的方式。

于 2013-05-10T14:10:36.190 回答
9

这取决于您如何查看扩展方法。鉴于它们只是常规静态方法之上的语法糖,我想说它们应该遵循静态方法的指导方针——检查所有参数,包括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使这些方法起作用。

于 2013-05-10T14:02:15.707 回答