C# 派生类是否应该隐藏默认值并创建从Comparer继承的静态成员<;T>;?

C# 派生类是否应该隐藏默认值并创建从Comparer继承的静态成员<;T>;?,c#,.net,icomparer,C#,.net,Icomparer,我正在编写一个IComparer实现,它是从Comparer类派生而来的,如下所示。例如: public class MyComparer : Comparer<MyClass> { private readonly Helper _helper; public MyComparer(Helper helper) { if (helper == null) throw new ArgumentNullExcepti

我正在编写一个
IComparer
实现,它是从
Comparer
类派生而来的,如下所示。例如:

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
    }
}
由于所需的
Helper
参数,我的比较器不能有默认实例,也不能从
比较中初始化自身,因此我无法用有意义的实现隐藏继承的静态成员

对于这种情况,建议的做法是什么?我正在考虑三种选择:

  • 手动实现
    IComparer
    ,而不是从
    Comparer
    派生,以避免继承所述静态成员

  • 保留继承的静态成员,并假设使用者知道不使用它们

  • 使用引发InvalidOperationException的新实现隐藏继承的静态成员:

    public static new Comparer<MyClass> Default
    {
        get { throw new InvalidOperationException(); }
    }
    
    public static new Comparer<MyClass> Create(Comparison<MyClass> comparison)
    {
        throw new InvalidOperationException();
    }
    
    公共静态新比较器默认值
    {
    获取{抛出新的InvalidOperationException();}
    }
    公共静态新比较器创建(比较)
    {
    抛出新的InvalidOperationException();
    }
    

    • 不从
      比较器继承。这是对代码共享继承的典型滥用。继承应该用来实现Liskov替换原则(LSP)。为代码重用而继承是一种黑客行为,因为正如您所发现的那样,它会在公共API界面中暴露“垃圾”

      这不是LSP冲突,因为未破坏任何基本类型的契约。然而,这是对继承的滥用。问题在于,API用户可能会错误地依赖内部构件的暴露方式。它还阻碍了将来的实现更改,因为删除基类可能会破坏用户

      您是否能够容忍这种污染取决于您对公共API表面的质量标准。如果你不在乎这一点,那就坚持干而不坚持LSP。如果有十亿行代码依赖于您的类,那么您肯定不想公开脏基类。这里的问题变成了封装(使用者不需要知道比较器的实现)和创建类时节省工作之间的权衡

      你提出了干燥原则。我不确定这是否违反了DRY的规定。Dry尝试防止重复的代码变得不一致,并尝试防止重复的维护工作。因为这里的重复代码永远不会改变(空排序是契约性的),所以我不认为这是一个有意义的干冲突。相反,它只是在创建实现时节省工作

      实现
      IComparer
      非常简单,所以要做到这一点。我认为不再需要实现
      IComparer
      。默认实现没有太多功能。如果您关心空输入,那么您必须在自己的比较方法中复制该逻辑。您实现的代码重用几乎是零

      我正在考虑实现我自己的ComparerBase

      这也是同样的问题。也许您可以改为创建一个实现样板文件null和类型处理的静态助手方法。该静态助手不会向API用户公开。这是“组合重于继承”

      隐藏静态成员确实令人困惑。根据调用站点的细微变化,将调用不同的方法。而且,它们都没有用处


      我不太关心静态方法现在可以通过不同的类型名使用。这些方法并不是真正继承的。它们仅作为C#功能提供。我相信这是为了版本弹性。从来都不建议这样做,各种工具都会围绕这一点发出警告。我不会太担心的。例如,每个
      都“继承”某些静态成员,例如
      Stream.Null
      Stream.Synchronized
      或任何它们被调用的对象。没有人认为这是一个问题。

      在我看来,不要对此采取任何行动,即你自己的选择:

      保留继承的静态成员,并假定 消费者将知道不要使用它们

      您的类还继承自
      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());
      
      因为
      对象
      是您编写的任何类型的基类,所以您永远无法避免这种情况

      您必须假设使用您的代码的开发人员理解
      静态成员(属性、方法等)在C#中的工作方式,也理解继承上下文中的工作方式

      好的开发工具(IDE)应该抱怨
      int.ReferenceEquals
      MyComparer.ReferenceEquals
      MyComparer.Default
      等等,因为这些都是编写调用的误导性方法

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


      与usr不同(参见其他答案)我认为
      Comparer
      是一个很好的基类。

      因此,请确保:您可以简单地实现
      IComparer
      接口,只需复制/粘贴
      IComparer.Compare
      方法,而不是对一个显然是为其他内容构建的类进行子类化?默认始终是一个默认实例。你不需要覆盖它。当有人使用它时,他们确实希望使用默认实例,而不是您自己的实例。为什么要跟踪这些成员?当用户使用Comparer.Default时,您也不能执行任何操作,因为它将调用基类中的属性。顺便说一句,我看不到从
      Comparer
      @xanatos继承的理由:这个类显然是为什么而构建的?MSDN
      Comparer
      :“我们建议您从
      Comparer
      类派生,而不是实现
      IComparer
      接口,因为
      Comparer
      类提供了一个显式接口
      // static method overload inherited from System.Object:
      MyComparer.Equals(new MyClass(), new MyClass());
      // also inherited from System.Object:
      MyComparer.ReferenceEquals(new MyClass(), new MyClass());