derived 类 是否应该隐藏从 Comparer<T> 继承的 Default 和 Create 静态成员?

Should derived classes hide the Default and Create static members inherited from Comparer<T>?

我正在编写一个 IComparer<T> 实现,方法是从 Comparer<T> class 派生为 recommended by MSDN。例如:

public class MyComparer : Comparer<MyClass>
{
    private readonly Helper _helper;

    public MyComparer(Helper helper)
    {
         if (helper == null)
             throw new ArgumentNullException(nameof(helper));

        _helper = helper;
    }

    public override int Compare(MyClass x, MyClass y)
    {
        // perform comparison using _helper
    }
}

但是,在这种方法下,MyComparer class 从 Comparer<T> class 继承了 DefaultCreate 静态成员。这是不可取的,因为上述成员的实现与我派生的 class 无关,并且可能导致误导行为:

// calls MyClass.CompareTo or throws InvalidOperationException
MyComparer.Default.Compare(new MyClass(), new MyClass());

由于需要 Helper 参数,我的比较器不能有默认实例,也不能从 Comparison<T> 初始化自己,所以我不能用有意义的实现隐藏继承的静态成员。

对于这种情况,推荐的做法是什么?我正在考虑三个选项:

不要继承自 Comparer<T>。这是对代码共享的继承的 classic 滥用。继承应该用于实现 Liskov 替换原则 (LSP)。继承以重用代码是一种黑客攻击,因为正如您发现的那样,它会在您的 public API 表面中公开 "junk"。

这不是 LSP 违规,因为没有基础类型合同被破坏。然而,这是对继承的滥用。问题是内部结构以 API 用户可能错误依赖的方式公开。它还阻碍了未来的实现更改,因为删除基础 class 可能会破坏用户。

您是否可以忍受这种脏污取决于您对 public API 表面的质量标准。如果您不关心这一点,那就继续坚持 DRY 而不是坚持 LSP。如果十亿行代码依赖于您的 class,您当然不想暴露一个脏基 class。这里的问题变成了封装(消费者不需要知道比较器的实现)和创建 class.

时节省工作之间的权衡。

你提出了DRY原则。我不确定这是违反 DRY 的实例。 Dry 试图防止重复的代码变得不一致,并试图防止重复的维护工作。由于此处的重复代码永远不会更改(空排序是合同规定的),我认为这不是有意义的 DRY 违规行为。相反,它只是在创建实现时节省工作。

实施 IComparer<T> 很容易,那就去做吧。我认为不再需要实施 IComparer。默认实现并不多。如果您关心空输入,则无论如何都必须在您自己的比较方法中复制该逻辑。您实现的代码重用几乎为零。

I'm thinking of implementing my own ComparerBase

那将是同样问题的一个例子。也许您可以创建一个静态辅助方法来实现样板 null 和类型处理。该静态助手不会暴露给 API 用户。这是 "composition over inheritance".

隐藏静态成员真的很混乱。根据调用站点的细微变化,将调用不同的方法。此外,其中 none 个很有用。

我不太关心现在可以通过不同的类型名称使用静态方法这一事实。这些方法并不是真正继承的。它们仅作为 C# 功能提供。我相信这是为了版本弹性。永远不推荐这样做,各种工具都会围绕此生成警告。我不会太担心这一点。例如,每个 Stream "inherits" 某些静态成员,例如 Stream.NullStream.Synchronized 或任何它们的名称。没有人认为这是一个问题。

在我看来,什么都不做,即你自己的选择:

Leave the inherited static members in place, and assume consumers will know not to use them

您的 class 也继承自 System.Object,因此拥有类似

的东西
// static method overload inherited from System.Object:
MyComparer.Equals(new MyClass(), new MyClass());
// also inherited from System.Object:
MyComparer.ReferenceEquals(new MyClass(), new MyClass());

你永远无法避免这一点,因为 object 是你编写的任何类型的基础 class。

您必须假定使用您的代码的开发人员了解 static 成员(属性、方法等)在 C# 中以及在继承。

好的开发人员工具 (IDE) 应该抱怨 int.ReferenceEqualsMyComparer.ReferenceEqualsMyComparer.Default 等等,因为这些是编写调用的误导方式。

new 隐藏成员几乎总是一个坏主意。根据我的经验,这让开发人员更加困惑。尽可能避免使用 new 修饰符(在类型成员上)。

与 usr 不同(参见其他答案)我认为 Comparer<> 是一个很好的基础 class 使用。