为什么要隐藏基类成员?

8

我刚学会了如何掩盖(使用new)基类成员,但是我不明白为什么要这样做。掩盖是否像封装一样为我们提供一定程度的保护?请指导。


2
不确定我理解问题,您能否提供一个掩盖基类成员的示例,以便我更好地理解您的意思。 - Lazarus
尽管派生类不能删除其继承的任何成员,但它可以隐藏它们。
  1. 要隐藏一个继承的成员,请声明一个相同类型和名称的新成员。
  2. 您还可以隐藏静态成员。
隐藏的优点是什么?
- Asterix
6个回答

3

我遇到的唯一几个有效而且安全的例子是更加明确地指定返回类型或在属性上提供一个set访问器。我不是说这些是唯一的,但这是我找到的全部。

例如,假设您有一个非常简单的基类,如下所示:

public abstract class Base
{
  public string Name { get; protected set; }

  public Base(string name)
  { Name = name; }
}

您可以创建一个派生类,看起来更像这样:
public class Derived : Base
{
  public new string Name 
  {
    get { return base.Name; }
    set { base.Name = value; }
  }

  public Derived(string name) : base(name)
  { }         
}

假设业务规则允许这个特定的派生类型有可变的名称,我认为这是可以接受的。使用new的问题在于它的行为取决于实例所表示的类型。例如,如果我这样说:

Derived d = new Derived("Foo");
d.Name = "Bar";
Base b = d;
b.Name = "Baz"; // <-- No set available.

在这个简单的例子中,我们可以轻松地处理。我们使用new来覆盖方法的行为,但没有破坏性地进行修改。如果要更改返回类型,则需要更加娴熟。特别是,如果你使用new在派生类型上更改返回类型,那么就不应该让基类型设置该类型。请看以下示例:
public class Base
{
  public Base(Base child)
  { Child = child; }

  public Base Child { get; private set; }
}

public class Derived
{
 public Derived(Derived child) : base(child)
 {  }

 public new Derived Child 
 { get { return (Derived)base.Child; } }

}

如果我可以在Base类上设置Child,那么在Derived类中就可能会出现转换问题。另一个例子:

Derived d = new Derived(someDerivedInstance);
Base b = d;
var c = b.Child;  // c is of type Base
var e = d.Child;  // e is of type Derived

我不能通过将所有的Derived类视为基类来违反任何业务规则,这只是不进行类型检查和转换的便利。


3

它不仅用于掩盖,实际上还会打破继承链,因此如果您调用基类方法,则不会调用派生类中的方法(只有基类中的方法)。

实质上,您正在创建一个与基类方法无关的方法。因此使用了 "new" 关键字。

记住,如果您想定义具有与基类型方法相同签名但返回类型不同的方法,则可以使用 "new" 关键字。


但这并不能真正解释为什么你想要那样做。(也就是说,你只是告诉我们你写的函数不被调用--如果它不被调用,那有什么意义呢?) - James Curran
为什么?如果您不希望新方法成为具有相同名称和参数类型的虚拟方法的继承链的一部分。作为副作用,它允许您重新声明具有相同签名但返回类型不同的方法。只要记住在调用基类型的方法时不会调用它即可。 - Philippe Leybaert
你的“作为副作用”的句子回答了问题。其余部分最好通过a) 给方法取一个不同的名称,或者b)根本不写来实现。 - James Curran

3
你很少会使用 "new" 来掩盖基类成员。
它主要用于派生类最先拥有该成员,然后将其添加到基类中 --- 为不同目的而使用相同的名称的情况。使用 new 的原因是你知道你正在以不同的方式使用它。当在 C++ 中添加一个基本成员时,它只会将现有方法静默地合并到继承链中。在 C# 中,你必须选择 newoverride 之间进行选择,以显示你知道发生了什么。

+1 这可能是最常见的用法,但我仍然不喜欢它,因为缺乏多态会破坏派生类的意图(以及其他可能的事情)。 - Marc

3
我刚学会了如何掩盖一个基类成员(使用new)
FYI,这个特性通常被称为“隐藏”,而不是“掩盖”。我认为“掩盖”是指在位数组中清除位。
我不明白为什么要这样做。
通常情况下你不想这么做。有关此功能的使用和不使用的一些原因,请参阅我2008年的文章:

http://blogs.msdn.com/b/ericlippert/archive/2008/05/21/method-hiding-apologia.aspx

在编程中,使用掩码是否像封装一样为我们提供了一定程度的保护?

不是的。


1

你所指的是名称隐藏。这主要是一种方便功能。如果你从一个你无法控制源代码的类继承,使用new将允许你改变一个方法的行为,即使它没有被声明为虚拟的(或者完全改变签名,如果它是虚拟的)。new关键字只是抑制了编译器警告。你基本上是在告诉编译器,你有意隐藏了父类的方法。

Delphi也有同样原因的reintroduce关键字。

除了抑制警告之外,这给你带来了什么好处?并不是很多。你不能从父类访问new方法。如果你的子类直接实现接口(而不是从其父类继承),你可以从接口访问它。你仍然可以从子类调用父类的成员。你的类的任何其他后代都将继承new成员,而不是父类中的成员。


0

这实际上被称为成员隐藏。有几种常见的情况可以适当地使用它。

  • 它允许您解决版本问题,其中基类或派生类作者无意中创建了与现有标识符冲突的成员名称。
  • 它可以用于模拟返回类型的协变。

关于第一点...基类的作者可能会稍后添加与派生类中现有成员相同名称的成员。基类作者可能不知道派生类,因此没有期望她应该避免名称冲突。C#支持使用隐藏机制独立演化类层次结构。

关于第二点...您可能希望一个类实现指定方法签名的接口,因此您只能返回特定类型的实例,同时您已经对该类型进行了子类化,并且真正希望调用者看到具体类型。考虑以下示例。

public interface IFoo { }

public class ConcreteFoo { }

public abstract class Base
{
  private IFoo m_Foo;

  public Base(IFoo x) { m_Foo = x; }

  public IFoo Foo { get { return m_Foo; } }
}

public class Derived
{
  public Derived(ConcreteFoo x) : base(x) { }

  public new ConcreteFoo Foo { get { return (ConcreteFoo)base.Foo; } }
}

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