允许使用显式接口实现在C#中隐藏成员吗?


14

我知道如何在C#中使用接口和显式接口实现,但是我想知道隐藏某些不经常使用的成员是否被认为是不好的形式。例如:

public interface IMyInterface
{
    int SomeValue { get; set; }
    int AnotherValue { get; set; }
    bool SomeFlag { get; set; }

    event EventHandler SomeValueChanged;
    event EventHandler AnotherValueChanged;
    event EventHandler SomeFlagChanged;
}

我事先知道这些事件虽然有用,但很少使用。因此,我认为通过显式实现将它们隐藏在实现类之外以避免IntelliSense混乱是有意义的。


3
如果通过接口引用实例,则无论如何都不会隐藏它们,只有在您引用的是具体类型时,它才会隐藏。
安迪

1
我和一个定期进行此操作的人一起工作。它使我绝对疯了。
乔尔·埃瑟顿

...并开始使用聚合。
mikalai 2014年

Answers:


39

是的,这通常是错误的形式。

因此,我认为通过显式实现将它们隐藏在实现类之外以避免IntelliSense混乱是有意义的。

如果您的IntelliSense太混乱,则您的课程太大。如果您的班级太大,可能会做太多事情和/或设计不当。

解决您的问题,而不是症状。


使用它来删除有关未使用成员的编译器警告是否合适?
2014年

11
“解决您的问题,而不是症状。” +1
FreeAsInBeer 2014年

11

使用此功能的目的。减少名称空间混乱不是其中之一。

合法的原因不是“隐藏”或组织,而是解决歧义和专业化。如果实现定义“ Foo”的两个接口,则Foo的语义可能会有所不同。除了显式接口外,实际上不是解决此问题的更好方法。另一种选择是不允许实现重叠的接口,或者声明接口不能关联语义(无论如何这只是概念上的事情)。两者都是不好的选择。有时实用性胜过优雅。

一些作者/专家认为这是一种功能的混乱(这些方法实际上并不是该类型的对象模型的一部分)。但是像所有功能一样,在某个地方,有人需要它。

同样,我不会将其用于隐藏或组织。有一些隐藏成员以使其免受智能感知的方法。

[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]

“有一些著名的作者/专家认为这是某项功能的败笔。” 谁是?建议的替代方案是什么?至少存在一个问题(即,子接口/实现类中接口方法的特殊化),这将需要向C#中添加一些其他功能,以便在没有显式实现的情况下解决它(请参阅ADO.NET和泛型集合) 。
2014年

@jhominal- 杰弗里·里希特(Jeffrey Richter)通过C#通过CLR 专门使用单词kludge。C ++在没有它的情况下相处融洽,程序员必须明确引用基本方法实现才能消除歧义或使用强制转换。我已经提到替代方案不是很吸引人。但是它是单一继承和接口的一个方面,而不是一般的OOP。
codenheim 2014年

您可能还已经提到,尽管Java具有相同的“实现+接口的单一继承”设计,但它也没有显式实现。但是,在Java中,重写方法的返回类型可以是专用的,从而避免了显式实现的部分需求。(在C#中,做出了一个不同的设计决策,可能部分是因为包含了属性)。
jhominal 2014年

@jhominal-好的,几分钟后,我会更新。谢谢。
codenheim 2014年

在类可能以符合接口约定但无用的方式实现接口方法的情况下,隐藏可能是完全合适的。例如,虽然我不喜欢类的含义,在类中IDisposable.Dispose做什么但被赋予了不同的名称,例如Close,但我认为对于一个类,其契约明确声明状态代码可以创建和放弃实例而无需调用Dispose使用显式接口实现来实现是完全合理的。定义IDisposable.Dispose什么都不做的方法。
2014年

5

我可能是混乱的代码的标志,但有时现实世界是混乱的。.NET框架的一个示例是ReadOnlyColection,它隐藏了变异的setter [],Add和Set。

IE ReadOnlyCollection实现IList <T>。另请参见 几年前我对这个问题的思考。


好吧,这也是我将要使用的我自己的界面,因此从一开始就没有做错任何意义。
凯尔·巴兰

2
我不会说它隐藏了变异设置器,而是根本没有提供它。更好的示例是ConcurrentDictionary,它IDictionary<T>显式实现的各种成员,以便ConcurrentDictionaryA)的使用者不会直接调用它们,而B)可以使用IDictionary<T>在绝对必要时需要实现的方法。例如,的用户ConcurrentDictionary<T> 致电TryAdd而不是Add避免不必要的异常。
布莱恩(Brian)

当然,Add明确实现也可以减少混乱。 TryAdd可以做任何事情都Add可以做(Add实现为对的调用TryAdd,因此同时提供两者将是多余的)。但是,TryAdd必须具有不同的名称/签名,因此如果IDictionary没有这种冗余就无法实现。显式实现可以彻底解决此问题。
布莱恩(Brian)

从消费者的角度来看,这些方法是隐藏的。
Esben Skov Pedersen 2014年

1
您撰写有关IReadonlyCollection <T>的文章,但链接到ReadOnlyCollection <T>。接口中的第一个,第二个是类。即使ReadonlyCollection <T>可以,IReadonlyCollection <T>也不实现IList <T>。
约尔根·福格

2

当成员在上下文中毫无意义时,这样做是一种很好的形式。例如,如果您创建一个只读集合,该集合IList<T>通过委派给一个内部对象来实现,_wrapped则您可能会遇到以下情况:

public T this[int index]
{
  get
  {
     return _wrapped[index];
  }
}
T IList<T>.this[int index]
{
  get
  {
    return this[index];
  }
  set
  {
    throw new NotSupportedException("Collection is read-only.");
  }
}
public int Count
{
  get { return _wrapped.Count; }
}
bool ICollection<T>.IsReadOnly
{
  get
  {
    return true;
  }
}

在这里,我们有四种不同的情况。

public T this[int index]是由我们的类而不是接口定义的,因此当然不是显式的实现,尽管请注意,它的确T this[int index]与接口中定义的读写相似,但是是只读的。

T IList<T>.this[int index]之所以是明确的,是因为其中的一部分(getter)与上面的属性完全匹配,而另一部分将始终引发异常。尽管对于通过接口访问此类实例的用户至关重要,但对于通过此类类型的变量使用此类实例的用户而言却毫无意义。

同样,因为bool ICollection<T>.IsReadOnly总是要返回true,所以针对类的类型编写的代码完全没有意义,但是对于通过接口的类型使用它至关重要,因此我们明确实现了它。

相反,public int Count未明确实现,因为它可能对通过自己的类型使用实例的某人有用。

但是对于您的“很少使用”情况,我确实非常倾向于不使用显式实现。

在我建议使用显式实现的情况下,通过类类型的变量调用该方法可能是错误(试图使用索引的setter)或毫无意义(检查将始终相同的值),因此隐藏它们可以保护用户免受错误或次优代码的侵害。这与您认为很少使用的代码有很大不同。为此,我可能会考虑使用该EditorBrowsable属性将成员从智能感知中隐藏起来,尽管我会感到厌倦。人们的大脑已经拥有自己的软件,可以过滤掉他们不感兴趣的内容。


如果要实现接口,请实现接口。否则,这显然违反了《里斯科夫换人原则》。
Telastyn 2014年

@Telastyn接口确实已实现。
乔恩·汉纳

但是它的契约并没有得到满足-特别是,“这代表了一个可变的,随机访问集合”。
Telastyn 2014年

1
@Telastyn履行记录在案的合同,特别是考虑到“只读集合不允许在创建集合后添加,删除或修改元素”。请参阅msdn.microsoft.com/en-us/library/0cfatk9t%28v=vs.110%29.aspx
Jon Hanna

@Telastyn:如果合同包含可变性,那么接口为什么要包含IsReadOnly属性?
nikie 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.