【问题标题】:When do Extension Methods break?扩展方法何时中断?
【发布时间】:2010-10-11 06:50:45
【问题描述】:

我们目前正在讨论 .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,并将使用它而不会抱怨不明确的扩展方法。哦,太恐怖了!

【问题讨论】:

  • 在我相信之前,我必须尝试一个新成员隐藏扩展方法的示例,而无需编译器窥视!我认为 D 的“重载集”概念是正确的。

标签: c# .net extension-methods


【解决方案1】:

.Net 调用的扩展方法也是有限形式的 MonkeyPatching(尝试忽略其中的 php 咆哮)。

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

【讨论】:

    【解决方案2】:

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

    【讨论】:

      【解决方案3】:

      要确保扩展方法不与其他方法(扩展或其他方法)冲突,您可以做的一件事是将FxCopPrevent Duplicate Extension Method Signatures 等规则一起使用。

      【讨论】:

        【解决方案4】:

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

        【讨论】:

          【解决方案5】:

          我不同意,扩展方法的全部意义在于将您的成员添加到黑盒类中。与其他所有事物一样,存在陷阱,您必须注意命名、实施并了解方法的优先顺序。

          【讨论】:

          • 可以说静态辅助类在特定场景中同样有用,而且危险性要小得多。
          • @Tobias 最后它也编译了一个静态类。如果扩展方法和实例方法具有相同的名称,则编译器必须做出决定。扩展方法很优雅。
          【解决方案6】:

          一些好奇心:

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

          编辑)当然还有“Nullable<T>/new()”炸弹(see here)...

          【讨论】:

            【解决方案7】:

            首先,我认为您的措辞有点误导。我认为您在谈论“类型”而不是“对象”。

            其次,扩展方法的一大优势是您可以将功能添加到您无法控制的类型。如果你控制了类型,为什么不直接修改类型而不是依赖扩展方法呢?

            【讨论】:

            • 感谢对象 -> 类型极客语法错误。优点很好理解。我们正在寻找危险。
            【解决方案8】:

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

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

            您不能将其用于:

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

            因为string.Concat 方法...

            【讨论】:

            • 通常静态调用扩展方法实际上会更好
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2021-11-19
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2011-11-15
            相关资源
            最近更新 更多