扩展方法什么时候破坏?

我们目前正在讨论.NET中的扩展方法是否坏。 或者在什么情况下,扩展方法可能会导致难以发现的错误或以任何其他方式出现意外行为。

我们想出了:

  • 为不受您控制的类型编写扩展方法(例如,使用GetTotalSize()扩展DirectoryInfo等)是不好的,因为API的所有者可能会引入隐藏扩展的方法 – 并且可能有不同的边缘情况。 例如,如果由于隐藏而不再使用扩展方法,则在扩展方法中对null进行测试将自动转换为NullReferenceException。

题:

  • 除了“隐藏”我们没有想到的还有其他危险情况吗?

编辑:

另一个非常危险的情况 假设您有一个扩展方法:

namespace Example.ExtensionMethods { public static class Extension { public static int Conflict(this TestMe obj) { return -1; } } } 

并使用它:

 namespace Example.ExtensionMethods.Conflict.Test { [TestFixture] public class ConflictExtensionTest { [Test] public void ConflictTest() { TestMe me = new TestMe(); int result = me.Conflict(); Assert.That(result, Is.EqualTo(-1)); } } } 

请注意,您使用它的命名空间更长。

现在你用这个引用一个dll:

 namespace Example.ExtensionMethods.Conflict { public static class ConflictExtension { public static int Conflict(this TestMe obj) { return 1; } } } 

而你的测试将失败! 它将编译而没有编译器错误。 它会失败 。 没有你甚至不必指定“使用Example.ExtensionMethods.Conflict”。 编译器将遍历命名空间名称并在Example.ExtensionMethods.Extension之前找到Example.ExtensionMethods.Conflict.ConflictExtension,并且将使用它而不会抱怨模糊的扩展方法 。 哦,恐怖!

一些好奇心:

  • 可以在null实例上调用扩展方法; 这可能会令人困惑(但有时很有用)
  • 如果他们有不同的意图,“隐藏”问题是一个大问题
  • 同样,您可能会从2个不同的命名空间中获得具有相同名称的不同扩展方法; 如果你只有两个名称空间中的一个,这可能会导致行为不一致(取决于哪个)……
  • …但是如果某人在您的代码使用的第二个命名空间中添加了类似 (相同签名)的扩展方法,它将在编译时中断(不明确)

编辑 )当然,还有“ Nullable / new() ”炸弹( 见这里 )……

我不同意,扩展方法的全部意义在于将您的成员添加到黑盒子类中。 像其他一切都有陷阱,你必须注意命名,实现和理解方法的啄食顺序。

我们刚刚在MoreLINQ项目中发现了一个破坏:如果你编写一个通用的扩展方法,就不可能确保它适用于所有类型。 我们有一个带有此签名的方法:

 public static IEnumerable Concat(this T head, IEnumerable tail) 

你不能用它:

 "foo".Concat(new [] { "tail" }); 

因为string.Concat方法…

我使用Ruby on Rails几乎和我使用C#一样长。 Ruby允许您执行类似于新扩展方法的操作。 当然,如果某人将该方法命名为相同,则存在潜在的问题,但是能够将方法添加到封闭类中的优势远远超过潜在的不利因素(这可能是由于糟糕的设计或糟糕的计划造成的)。

要确保扩展方法不与其他方法(扩展或其他方法)冲突,您可以做的一件事是将FxCop与诸如防止重复扩展方法签名之类的规则一起使用。

首先,我认为你的措辞有点误导。 我认为你说的是​​“类型”,而不是“对象”。

其次,扩展方法的最大优点是您可以添加要控制的类型的function。 如果您控制类型,为什么不修改类型而不是依赖于扩展方法?

我们在团队中采取的态度是,扩展方法非常有用,你无法实际禁止它们,但是如此危险(主要是因为隐藏问题)你必须要谨慎一点。 因此我们决定所有扩展方法名称都必须以X为前缀(因此我们有一堆XInit...()方法以有用的方式初始化控件)。 这样的方式a)减少了命名冲突的可能性,并且b)程序员知道他正在使用扩展方法而不是类方法。

什么.Net调用扩展方法也是限制forms的MonkeyPatching (尝试忽略那里的php咆哮)。

这应该会为你的讨论提供一些材料。