为什么我不能拥有受保护的接口成员?

90

声明接口中的受保护访问成员有什么反对意见?例如,以下代码是无效的:

public interface IOrange
{
    public OrangePeel Peel { get; }
    protected OrangePips Seeds { get; }
}
在这个例子中,接口IOrange至少能够保证实现者为其继承者提供一个OrangePips实例。如果实现者希望的话,他们可以将范围扩展到完全public
public class NavelOrange : IOrange
{
    public OrangePeel Peel { get { return new OrangePeel(); } }
    protected OrangePips Seeds { get { return null; } }
}

public class ValenciaOrange : IOrange
{
    public OrangePeel Peel { get { return new OrangePeel(); } }
    public OrangePips Seeds { get { return new OrangePips(6); } }
}

protected修饰接口成员的意图是为了为继承者(子类)提供支持合同,例如:

public class SpecialNavelOrange : NavelOrange
{
    ...
    // Having a seed value is useful to me.
    OrangePips seeds = this.Seeds; 
    ...
}

(诚然,这对于struct是不起作用的)

我认为在接口中使用privateinternal修饰符没有太多意义,但支持publicprotected修饰符似乎非常合理。


我将尝试通过将interfaceprotected成员分开来解释interfaceprotected成员的实用性:

假设有一个新的C#关键字support,用于强制继承者遵守合约,我们可以按照以下方式声明:

public support IOrangeSupport
{
    OrangePips Seeds { get; }
}

这将使我们能够缩小类,以便为它们的继承者提供受保护的成员:

public class NavelOrange : IOrange, IOrangeSupport
{
    public OrangePeel Peel { get { return new OrangePeel(); } }
    protected OrangePips Seeds { get { return null; } }
}

这并不特别有用,因为类已经通过提供protected成员隐含了这个约定。

但是我们也可以这样做:

public interface IOrange : IOrangeSupport
{
   ...
}

因此,将 IOrangeSupport 应用于所有实现 IOrange 的类,并要求它们提供特定的 protected 成员——这是目前我们无法做到的。


可能是Non Public Members for C# Interfaces的重复问题。 - nawfal
1
为了完整阐述这个问题,请考虑以下用例。我有一个派生类,继承自一个通用基类。我想添加一个受保护的成员,可以在派生类的任何通用版本上访问,但不对外公开。class Base<T> { } interface IDerived { string Secret { get; set; } } class Derived<T> : Base<T>, IDerived { protected string Secret; protected void LearnSecret(IDerived other) { var x = other.Secret; } } - hypehuman
我很惊讶没有任何答案或评论提到EIMI。当成员从实现类型的角度来看时,EIMI使接口成员变为私有。 - qqqqqqq
1
可能会在C# 8.0中出现 https://jeremybytes.blogspot.com/2019/11/c-8-interfaces-public-private-and.html - Doug Domeny
15个回答

71

我认为大家都重点强调了接口只具有公共成员,没有实现细节。你正在寻找的是抽象类

public interface IOrange
{
    OrangePeel Peel { get; }
}

public abstract class OrangeBase : IOrange
{
    protected OrangeBase() {}
    protected abstract OrangePips Seeds { get; }
    public abstract OrangePeel Peel { get; }
}

public class NavelOrange : OrangeBase
{
    public override OrangePeel Peel { get { return new OrangePeel(); } }
    protected override OrangePips Seeds { get { return null; } }
}

public class ValenciaOrange : OrangeBase
{
    public override OrangePeel Peel { get { return new OrangePeel(); } }
    protected override OrangePips Seeds { get { return new OrangePips(6); } }
}

编辑:可以说,如果我们有一个派生自装饰品类Ornament的PlasticOrange,它只能实现IOrange而不能实现Seeds受保护方法。那没关系。接口从定义上来说是调用方和对象之间的合约,而不是类和其子类之间的合约。抽象类是我们接近这个概念的方式。这样很好。你基本上提出的是另一种语言构造,通过它我们可以将子类从一个基类切换到另一个基类而不会破坏构建。对我来说,这没有意义。

如果你正在创建一个类的子类,那么子类就是基类的专门化。它应该完全了解基类的任何受保护成员。但是,如果你突然想要更改基类,那么子类就不能与其他任何IOrange一起使用,这没有意义。

我想你问得很公正,但这似乎是一个特例,老实说我看不出其中的任何好处。


11
只有当将NavelOrangeValenciaOrange的基类设置为相同的内容时,抽象类才能起作用。假设已经从Ornament派生出一个名为PlasticOrange的类。 - ajlane
5
那我会怀疑这个橙子是否有籽或者能否剥皮。说真的,这是一个公正的观点,但我们受到OOP单继承模型的限制,所以我没有好的答案。话虽如此,这个解决方案解决了原始问题,除非你打算创建你所暗示的模型。 :) - Szymon Rozga

57

看不出为什么会有人需要这个。如果你想让派生类提供特定方法的实现,可以选择抽象基类。接口只是接口。一个公共契约,仅此而已。将接口视为描述实现在外部世界中应该如何呈现的规范。一个两针插头的规范并没有说明它的内部结构应该是什么样子(至少我认为是这样)。它只需要与插座兼容即可。

Plug
(来源: made-in-china.com)

14
最好的接口解释,我听过的。+1 - WolfmanDragon
9
我认为了解该插头背后的实施是否需要110V或220V电流非常重要;-) - mindplay.dk
5
在一个完美的世界中,110伏和220伏的插头应该是不同的。 - Anton Gogolev
6
在我的完美世界里,所有设备都能在110伏和220伏的电压下同样良好地工作,插头也都是一样的;-) - mindplay.dk
2
接口保证了对外客户端的实现。如果类A实现了接口B,那么你可以确定类A已经实现了接口B的方法。因此,你可以将A视为B,并保证它将像B一样工作。抽象类保证了内部客户端(即从中派生的类)的实现,但它也允许部分实现。正是这种双重用途是有问题的。我认为应该将抽象类分成两个构造。一个用于不允许多重继承的部分实现,另一个用于派生契约。 - Didier A.
显示剩余2条评论

16

这是没有意义的。接口是一个公开暴露的合约。我是一个IThing,因此如果被要求,我将执行IThing方法。但你不能要求IThing确认它执行了它无法告诉你的方法。


但是,IThing可以告诉它的继承者有关非“public”方法的信息。为什么我不能在这种情况下要求它执行其他IThing方法呢? - ajlane
因为那些不是IThing方法,而是基类的方法/属性。如果两个IThing实现都实现了这个受保护成员,但第三个实现没有,因为它根本没有意义。那么怎么办? - Szymon Rozga
@siz:第三个必须实现它:它在接口中。 - ajlane
@annakata:假设AbstractThing实现了IThing接口,而ConcreteThing则派生自AbstractThing。AbstractThing可以向ConcreteThing公开受保护的(非公共的)成员。这个想法是IThing也可以指定其中的一些成员。 - ajlane
@AJ - 但是这仍然是一个实现特定的细节,您已经可以通过抽象来强制实现具体。接口和抽象以非常不同的原因执行一些相同的操作。 - annakata
显示剩余2条评论

11

10

接口存在的目的是让人们可以访问您的类而不知道具体实现。它完全将实现与数据传递合同分离。

因此,接口中的所有内容必须是公共内容。非公共成员仅在您可以访问实现的情况下有用,并且因此对接口定义没有实质性贡献。


8

接口成员是公共API;像protected等东西是实现细节 - 而接口没有任何实现。我猜你要找的是显式接口实现:

public class NavelOrange : IOrange
{
    public OrangePeel Peel { get { return new OrangePeel(); } }
    OrangePips IOrange.Seeds { get { return null; } }
}

显式接口实现仍然是“public”的,所以这并不是我要找的。具体来说,我认为“protected”成员将为基类集和它们的继承者之间提供API。 - ajlane
这严格限制于基类和继承者之间 - 它根本与接口无关。如果您想在基类中使用受保护的方法,只需声明一个即可。 - Marc Gravell
没错,但是假设我们有许多实现该接口的基础类。定义一份协议来说明任何继承自这些类的人都可以期望获得某种标准的支持似乎是合理的。 - ajlane

3

3

接口就像钥匙的形状一样。

enter image description here

它不是钥匙。

它也不是锁。

它只是一个细小的接触点。

因此,定义钥匙形状的接口的所有成员都必须是公共的。

要让钥匙打开锁,它们必须共享相同的形状。

通过将形状(接口)公开,您可以让其他人创建兼容的锁或兼容的钥匙。

否则,将其(接口)声明为内部的,您将无法允许其他人创建兼容的锁或兼容的钥匙。


在你的图表中,所有的联系点都是内部的。没有理由让这些联系点总是公开的。C#只是这样做而已。 - Suncat2000
有人说前面的答案是接口最好的解释,但我不同意。这个更简洁、更清晰。 - Paul-Sebastian Manole

1
接口是向客户端承诺某些功能的契约。换句话说,接口的目的是能够将类型转换为它并像那样传递给需要该接口保证功能的代码。由于类型的客户端代码无法访问该类型的受保护成员,因此在接口中声明受保护项是没有意义的。

1

与抽象基类相比,受保护的接口将允许“多重继承”(抽象类),这在某些情况下可能会很有用...


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接