我正在按照 MSDN 的建议,通过从 Comparer<T>
类派生来编写IComparer<T>
实现。例如:
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
类继承Default
并从Comparer<T>
类Create
静态成员。这是不可取的,因为上述成员的实现与我的派生类无关,并且可能导致误导行为:
// calls MyClass.CompareTo or throws InvalidOperationException
MyComparer.Default.Compare(new MyClass(), new MyClass());
由于所需的 Helper
参数,我的比较器不能有默认实例,也不能从Comparison<T>
初始化自身,所以我无法使用有意义的实现隐藏继承的静态成员。
对于这种情况,建议的做法是什么?我正在考虑三种选择:
手动实现
IComparer<T>
,而不是从Comparer<T>
派生,以避免继承所述静态成员将继承的静态成员保留在原位,并假设使用者知道不使用它们
使用抛出
InvalidOperationException
的新实现隐藏继承的静态成员:public static new Comparer<MyClass> Default { get { throw new InvalidOperationException(); } } public static new Comparer<MyClass> Create(Comparison<MyClass> comparison) { throw new InvalidOperationException(); }
不要从Comparer<T>
继承。这是代码共享对继承的经典误用。继承应该用于实现利斯科夫替代原则(LSP)。继承代码重用是一种黑客攻击,因为当你发现它时,它会在你的公共 API 图面中公开"垃圾"。
这不是 LSP 违规,因为没有违反基本类型合同。然而,这是对继承的滥用。问题在于,内部结构正在以API用户可能错误依赖的方式公开。它还阻碍了将来的实现更改,因为删除基类可能会中断用户。
是否可以容忍这种脏污取决于公共 API 图面的质量标准。如果您不关心这一点,请继续遵守 DRY,同时不遵守 LSP。如果有十亿行代码依赖于你的类,你当然不想公开一个脏的基类。这里的问题变成了封装(使用者不需要知道比较器的实现)和创建类时节省工作之间的权衡。
你提出了 DRY 原则。我不确定这是违反 DRY 的实例。Dry 尝试防止重复的代码变得不一致,并尝试防止重复的维护工作。由于此处的重复代码永远不会更改(空排序是合同性的),我认为这不是有意义的 DRY 违规行为。相反,它只是在创建实现时节省工作。
实现IComparer<T>
很容易,所以这样做。我认为没有必要再实施IComparer
了。默认实现不多。如果您关心空输入,则必须在自己的比较方法中复制该逻辑。您实现的代码重用几乎为零。
我正在考虑实现我自己的比较器库
这将是同一问题的情况。也许您可以改为创建一个静态帮助程序方法来实现样板 null 和类型处理。该静态帮助程序不会向 API 用户公开。这就是"组合重于继承"。
隐藏静态成员确实令人困惑。根据调用站点的细微变化,将调用不同的方法。此外,它们都没有用。
我不太关心静态方法现在可以通过不同的类型名称使用的事实。这些方法并不是真正继承的。它们仅作为 C# 功能提供。我相信这是为了版本控制弹性。从不建议这样做,并且各种工具会围绕此生成警告。我不会太担心这一点。例如,每个Stream
都"继承"某些静态成员,例如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());
你永远无法避免这一点,因为object
是你编写的任何类型的基类。
您必须假定使用代码的开发人员了解static
成员(属性、方法等)在 C# 中的工作方式,包括在继承上下文中也是如此。
好的开发人员工具(IDE)应该抱怨int.ReferenceEquals
,MyComparer.ReferenceEquals
,MyComparer.Default
等,因为这些是编写调用的误导性方式。
用new
隐藏成员几乎总是一个坏主意。根据我的经验,它使开发人员更加困惑。尽可能避免使用 new
修饰符(在类型成员上)。
与usr不同(见其他答案),我认为Comparer<>
是一个很好的基类。