Есть ли причина скрывать унаследованные члены в интерфейсе?

Я понимаю, что класс, унаследованный от другого класса, может скрыть свойство с помощью ключевого слова new. Однако это скрывает конкретную реализацию свойства, поэтому я могу понять, как ее можно использовать.

Есть ли практическая причина скрывать элементы в интерфейсах, реализующих другие интерфейсы? Например, рассмотрим пример ниже. IChildInterface реализует IParentInterface и прячет PropertyA.

interface IParentInterface
{
    string Name { get; set; }
    int PropertyA { get; set; }
    int PropertyB { get; set; }
}

interface IChildInterface : IParentInterface
{
    int PropertyA { get; set; }
    int PropertyC { get; set; }
}

person Eric Anastas    schedule 25.08.2010    source источник


Ответы (5)


Есть ли практическая причина скрывать элементы в интерфейсах, реализующих другие интерфейсы?

Конечно. Тот факт, что сам BCL использует этот шаблон, свидетельствует о его практическом применении. Например:

interface IEnumerable 
{ 
    IEnumerator GetEnumerator();
}
interface IEnumerable<T> : IEnumerable
{
    new IEnumerator<T> GetEnumerator();
}

Разработчики IEnumerable<T> хотели иметь обратную совместимость с IEnumerable, но также хотели, чтобы каждое использование GetEnumerator в универсальном интерфейсе вызывалось универсальной версией. В этом случае уместным механизмом является сокрытие.

Дополнительное обсуждение тонких моментов, связанных с сокрытием методов, см. В следующих разделах:

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

person Eric Lippert    schedule 25.08.2010

Один из случаев, когда я нашел полезным скрытие базового члена, - это когда у вас есть базовый интерфейс, который предоставляет средство получения для свойства, но производный интерфейс хочет предоставить средство установки:

public interface IBase
{
   int MyProperty { get; }
}

public interface IDerive : IBase
{
    // you need to specify the getter here too
    new int MyProperty { get; set; }
}
person theburningmonk    schedule 25.08.2010

Интерфейсы не могут полностью скрыть родительские интерфейсы, но класс реализации может, что может быть полезно.

Рассмотрим класс MyStringList, который является списком только для чтения, который реализует IList<string>. Для простоты мы будем использовать его как простой проход: некоторые из членов довольно бессмысленны, поэтому мы можем сделать следующее:

//implement this one implicitly, as it's useful.
public int Count
{
  return _list.Count;
}
//do a half-and-half on the indexer
public string this[int index]
{
  get
  {
    return _list[index];
  }
}
string IList<string>.this[int index]
{
  get
  {
    return this[index];
  }
  set
  {
    throw new NotSupportedException("Collection is read-only.");
  }
}
//hide some pointless ones.
bool ICollection<string>.IsReadOnly
{
  get
  {
    return true;
  }
}
void IList<string>.Insert(int index, string item)
{
  throw new NotSupportedException("Collection is read-only.");
}
void IList<string>.RemoveAt(int index)
{
  throw new NotSupportedException("Collection is read-only.");
}
void ICollection<string>.Add(string item)
{
  throw new NotSupportedException("Collection is read-only.");
}
void ICollection<string>.Clear()
{
  throw new NotSupportedException("Collection is read-only.");
}
bool ICollection<string>.Remove(string item)
{
  throw new NotSupportedException("Collection is read-only.");
}

Кто-то, имеющий дело с интерфейсом с MyStringList по IList<string>, должен иметь возможность вызывать эти бессмысленные элементы, но нет необходимости для кого-то, имеющего дело с MyStringList, делать это.

Теперь, когда это возможно для класса, интерфейс может навязать такое значение классу путем сопоставления имени родительского интерфейса. Примером класса является IEnumberable<T>, где GetEnumerator() совпадает с классом IEnumerable, от которого он наследуется. Следовательно, класс может неявно реализовывать только один и должен скрывать другой или оба (поскольку один всегда может привести результат (IEnumerator<T>) к типу результата другого (IEnumerator), то наиболее разумным поведением обычно является неявная реализация версия IEnumberable<T> и явно реализует IEnumerable одну (обычно путем возврата результата другой версии).

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

person Jon Hanna    schedule 25.08.2010

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

Тем не менее, иногда бывает удобно скрыть унаследованный метод с помощью эквивалентного метода, который возвращает более конкретный тип. В этих случаях единственное, что вы делаете, - это сахар приведения типов - будьте осторожны, чтобы не изменить семантику метода, потому что вызывающие могут так же легко вызвать метод-предок вместо вашего.

См. Также: Существует ли ситуация, когда производный класс должен скрыть…?

person dthorpe    schedule 25.08.2010
comment
Я согласен, когда дело касается классов (включая исключение, которое вы указываете), но я думаю, что дело обстоит иначе, когда дело касается интерфейсов. - person Jon Hanna; 26.08.2010

Я полагаю, если бы вы собирались реализовать IChildInterface явно, возможно, вы захотели бы скрыть PropertyA (возможно?)

person taylonr    schedule 25.08.2010
comment
Но поскольку IChildInterface реализует IParentInterface, разве какие-либо реализации IChildInterface (неявные или явные) также реализуют IParentInterface? - person Eric Anastas; 26.08.2010
comment
Да, думаю, теперь, когда я думаю об этом, ты, наверное, прав. - person taylonr; 26.08.2010