Зачем вам маскировать члена базового класса? - PullRequest
8 голосов
/ 31 августа 2010

Я только что узнал, как замаскировать члена базового класса (используя новый), но мне не хватает того, почему я хотел бы это сделать. Обеспечивает ли маскирование определенный уровень защиты, как в случае использования инкапсуляции? Пожалуйста, сообщите.

Ответы [ 6 ]

3 голосов
/ 31 августа 2010

Я только что узнал, как замаскировать члена базового класса (используя новый)

К вашему сведению, эта функция обычно называется «скрытием», а не «маскировкой».Я думаю о «маскировании» как об очистке битов в массиве битов.

упускает из виду то, почему я хотел бы сделать это.

Обычно вы этого не хотите.По некоторым причинам, чтобы использовать и не использовать эту функцию, см. Мою статью на эту тему за 2008 год:

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

Обеспечивает ли маскировка определенный уровень защиты, как естьслучай использования инкапсуляции?

Нет, нет.

3 голосов
/ 31 августа 2010

Примеры only valid safe , с которыми я сталкивался, более конкретны с типами возвращаемых значений или предоставляют набор методов доступа для свойства. Я не говорю, что это единственные, но это все, что я нашел.

Например, предположим, у вас есть очень простая база, которая выглядит следующим образом:

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; } }

}

Если бы я мог set Child в классе Base, у меня могла бы быть проблема приведения в классе 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 голосов
/ 31 августа 2010

Вы очень редко будете использовать «новый» для маскировки члена базового класса.

Он в основном используется для случаев, когда у производного класса сначала был член, а затем он был добавлен в базовый класс - то же имя для другой цели. new означает, что вы признаете, что используете его по-другому. Когда базовый член добавляется в C ++, он просто незаметно объединяет существующий метод в цепочку наследования. В C # вам придется выбирать между new и override, чтобы показать, что вы знаете, что происходит.

3 голосов
/ 31 августа 2010

Используется не только для маскировки.Это фактически разрывает цепочку наследования, поэтому, если вы вызовете метод базового класса, метод в производном классе не будет вызван (только тот, который в базовом классе).

По сути, вы создаете новый метод, который не имеет ничего общего с методом базового класса.Следовательно, ключевое слово «new».

Помня об этом, ключевое слово «new» можно использовать, если вы хотите определить метод с такой же сигнатурой, как у метода базового типа, но с другим типом возврата.

1 голос
/ 31 августа 2010

То, на что вы ссылаетесь, называется Имя скрывается .Это в основном удобная функция.Если вы наследуете от класса, для которого вы не управляете источником, использование new позволит вам изменить поведение метода, даже если он не был объявлен как виртуальный (или полностью изменить сигнатуру, если он виртуальный).Ключевое слово new просто подавляет предупреждение компилятора.Вы в основном сообщаете компилятору, что вы намеренно скрываете метод от родительского класса.

У Delphi по той же причине было ключевое слово reintroduce.

Что это покупает, кромеподавленное предупреждение?Не очень много.Вы не можете получить доступ к методу new из родительского класса.Вы можете получить к нему доступ через интерфейс, если ваш дочерний класс непосредственно реализует интерфейс (в отличие от наследования его от родительского класса).Вы все еще можете вызвать члена родительского класса от ребенка.Любые дополнительные потомки вашего класса будут наследовать член new, а не тот, который находится в родительском элементе.

0 голосов
/ 31 августа 2010

Это на самом деле называется скрытием члена.Существует несколько распространенных сценариев, в которых это можно использовать соответствующим образом.

  • Это позволяет обойти проблемы управления версиями, в которых автор базового или производного класса невольно создает имя члена, которое вступает в противоречие с существующимидентификатор.
  • Может использоваться для имитации ковариации типов возвращаемых данных.

Относительно первого пункта ... возможно, что автор базового класса мог позже добавить членас тем же именем, что и существующий член в производном классе.Автор базового класса может не знать о производных классах, и поэтому не следует ожидать, что она должна избегать конфликтов имен.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; } }
}
...