想象一下,我们已经定义了一个采用这种形式的扩展方法:
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()
抛出一个NullReferenceException
,foo2
显然是null
。上述矛盾引出了一些结论。第二点错过了一个重要的问题 - 堆栈跟踪。在标准NullPointerException
情况下,堆栈跟踪直接指向foo1.Bar(0)
行。在扩展方法中,它将指向抛出异常的扩展方法中的行。因此,一致行为仍然存在不一致的堆栈跟踪。
现在问题 - 关于无效安全性,“最佳实践”如何适用于第三方将要使用的扩展方法?我们是否应该通过在@this
参数上添加参数空值验证来忽略一致性?或者它是一个可以让我们绕过良好实践建议的角落案例?
修改
我正在解决一个带有扩展名的库将被暴露的情况。它不会使用非内置/第三方解决方案,如PostSharp或其他类似技术。还需要与.NET 3.5完全兼容。
答案 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开发人员习惯的。