C#是否有私有和受保护的继承概念?

47
C# 拥有私有/受保护继承的概念吗?如果没有,为什么?

C++

class Foo : private Bar {
 public:
   ...
 }; 

C#

public abstract class NServlet : private System.Web.UI.Page
{
    // 错误:“需要类型”
}

我正在一个.aspx页面中实现类似servlet的概念,而且我不想让具体类能够看到System.Web.UI.Page基类的内部。


你是否更愿意直接实现 IHttpHandler 来实现类似于 Servlet 的概念呢? - Jordão
10个回答

22

C#仅允许公共继承。C++允许所有三种类型的继承。公共继承意味着一种“IS-A”的关系,而私有继承则意味着一种“在某种程度上是通过实现实现的”关系。由于分层(或组合)可以以更简单的方式完成这一点,因此只有当需要受保护的成员或虚函数要求时,才会使用私有继承 - 根据Scott Meyers在Effective C++,条款42中的说法。

我的猜测是,C#的作者们并不认为这种实现一个类以另一个类为基础的额外方法是必要的。


4
在这种情况下,你可以简单地使用组合,所以我认为他们是正确的。 - peSHIr
9
它还可以表示“IS-A”的意思,但仅限于类的范围。此外,在C++中即使是私有/保护继承的情况下,派生类也可以重写虚成员。组合并不总是私有继承的替代品,我有时会在C#中怀念这个关键特性。 - Alexandre C.

8
不,它不会。允许这种类型的限制有什么好处呢?
私有和保护继承有利于封装(信息隐藏)。C++支持保护*继承,但Java不支持。以下是一个我项目中会用到的例子。
在第三方框架**中有一个基类。它有几十个设置以及操作这些设置的属性和方法。当单独分配设置时,基类并没有做太多的检查,但如果遇到不可接受的组合,则稍后会生成异常。
我正在编写一个子类,其中包含用于分配这些设置的方法(例如,从文件中分配精心制作的设置)。拒绝其余代码(我的子类之外的代码)能够操纵单个设置并将其弄乱将是很好的。
话虽如此,在C++中(再次提醒,支持私有和保护继承),可能会将子类向上转换为父类并访问父类的公共成员。(请参见Chris Karcher的帖子)但是,保护继承可以改善信息隐藏。如果需要将类B1的成员在其他类C1和C2中真正隐藏起来,可以通过在C1和C2中创建一个保护变量的B1类来实现。B1类的受保护实例将可供C1和C2的子类使用。当然,这种方法本身并没有为C1和C2之间提供多态性。但是,如果需要,可以通过从通用接口I1继承C1和C2来添加多态性。
***为简洁起见,将使用“protected”而不是“private and protected”。
**这是我的情况下,国家仪器测量工作室(National Instruments Measurement Studio)。

好的,那正是我的情况。父类有很多属性,我想在不使用组合的情况下隐藏它们。 - CharlesB
1
这是一个非常好的对比私有继承和封装的论点的分析。 - Alain
1
我认为在C++中,可以将子类向上转型为父类,并访问父类的公共成员。- 不行,私有成员是私有的。 - denis
@gdy 确实,我刚刚检查了一下,g++对我的调用static_cast<A>(b).f()报错:"error: 'A' is an inaccessible base of 'B'"。 - jrsala

5

您可以通过在您的类中声明相同成员为私有并使用新关键字来隐藏继承的API,使其不被公开可见。请参阅MSDN上的继承隐藏


2
但是,然后你必须从新成员调用原始成员以恢复原始成员的功能。这似乎不是特别干净的方法。更好的方法可能是使用组合而不是继承,在后代类中仅公开要公开的成员作为公共成员。 - Robert Harvey
@RobertHarvey 但是当谈论到具有大型API的类时,例如Dictionary<K,V>,这也是很痛苦的。 - NetMage

3
如果您希望NServlet类不知道页面的任何信息,可以考虑使用适配器模式。编写一个页面来托管NServlet类的实例。根据您的具体需求,您可以编写许多只知道基类NServlet而不必在API中污染ASP.NET页面成员的类。

2

@bdukes: 请记住,您并没有真正隐藏该成员。例如:

class Base
{
   public void F() {}
}
class Derived : Base
{
   new private void F() {}
}

Base o = new Derived();
o.F();  // works

但是这与C ++中的私有继承相同,这正是提问者想要的。

1
实际上并不是这样。Base 的私有继承将不允许调用 o.F() - NetMage
就像@NetMage所说,这是不够的,也无法隐藏对F的访问权限。 - JHBonarius

1

第一种解决方案:

protected internal 在同一程序集中作为公共访问,而在其他程序集中作为受保护的访问。

您需要更改类的每个成员的访问修饰符,以便不通过继承暴露它们。

虽然这种解决方案有点限制,因为需要和强制该类被继承才能被另一个程序集使用。因此,选择仅通过继承或不通过继承使用是由不知情的父级来做出的...通常子类更了解架构...

这不是一个完美的解决方案,但可能是一个更好的替代方法,用于隐藏方法而不是添加接口,并仍然保留使用父方法被隐藏的可能性,因为您可能无法轻松地强制使用接口。


问题: protected和private访问修饰符不能用于实现接口的方法。这意味着受保护的内部解决方案不能用于接口实现的方法。这是一个很大的限制。

最终解决方案:

我选择了使用接口来隐藏方法的方案。

它的问题在于如何强制使用接口,以便要隐藏的成员始终处于隐藏状态,并最终避免错误。

为了强制仅使用接口,只需将构造函数设为受保护的,并添加一个静态方法用于构建(我将其命名为New)。这个静态的New方法实际上是一个工厂函数,它返回接口。因此,代码的其余部分必须仅使用接口!


1

不,只有公共继承。


1
你可能需要一个ServletContainer类,该类可以与NServlet实现进行连接。在我看来,不允许私有/受保护的继承并不是什么大问题,并且使语言更加清晰易懂 - 随着LINQ等工具的出现,我们已经有足够多的东西要记住了。

1

我知道这是一个老问题,但在编写C#时我遇到了这个问题好几次,我想知道...为什么不使用接口呢?

当您创建第三方框架类的子类时,还要让它实现一个公共接口。然后定义该接口仅包括您希望客户端访问的方法。然后,当客户端请求该类的实例时,给他们一个该接口的实例。

这似乎是C#中做这些事情的一种被接受的方式。

我第一次这样做是当我意识到C#标准库没有字典的只读变体时。我想提供对字典的访问,但不想让客户端能够更改字典中的项目。因此,我定义了一个“class DictionaryEx<K,V,IV> : Dictionary<K,V>, IReadOnlyDictionary<K,IV> where V : IV”,其中K是键类型,V是实际值类型,IV是防止更改的V类型的接口。 DictionaryEx的实现大多是直截了当的;唯一困难的部分是创建一个ReadOnlyEnumerator类,但即使那也没有花费很长时间。

我能看到这种方法唯一的缺点是,如果客户端尝试将您的公共接口动态转换为相关的子类,则无法实现。为了防止这种情况发生,请将您的类设置为内部类。如果您的客户端将您的公共接口转换为原始基类,我认为他们会非常清楚地意识到自己在冒险。 :-)

-3

不,它不会。允许这种类型的限制有什么好处呢?


2
允许私有继承并不是一种限制,禁止它才是。这样做的好处与C++提供的类似。 - Thomas Eding
1
好的架构可以带来长期的好处,比如通过减少依赖关系,使代码更易于维护和重构。当User1、User2类只能使用Child类而不能使用Parent类时,您可以修改Parent类而不需要对User1或User2进行任何更改... - franckspike

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