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继承的理由:这个类显然是为什么而构建的?MSDNComparer
:“我们建议您从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());