Допустимо ли использовать явную реализацию интерфейса для скрытия членов в C #?

14

Я понимаю, как работать с интерфейсами и явной реализацией интерфейса в C #, но мне было интересно, считается ли плохой формой прятать некоторых членов, которые не будут использоваться часто. Например:

public interface IMyInterface
{
    int SomeValue { get; set; }
    int AnotherValue { get; set; }
    bool SomeFlag { get; set; }

    event EventHandler SomeValueChanged;
    event EventHandler AnotherValueChanged;
    event EventHandler SomeFlagChanged;
}

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

Кайл Баран
источник
3
Если вы ссылаетесь на свой экземпляр через интерфейс, он все равно не будет их скрывать, он будет скрыт, только если ваша ссылка относится к конкретному типу.
Энди
1
Я работал с парнем, который делал это на регулярной основе. Это сводило меня с ума.
Джоэл Этертон,
... и начать использовать агрегацию.
Микалай

Ответы:

39

Да, это вообще плохая форма.

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

Если ваш IntelliSense слишком перегружен, ваш класс слишком велик. Если ваш класс слишком большой, он, вероятно, делает слишком много вещей и / или плохо спроектирован.

Исправьте свою проблему, а не свой симптом.

Telastyn
источник
Уместно ли использовать его для удаления предупреждений компилятора о неиспользуемых членах?
Гусдор
11
«Исправь свою проблему, а не симптом». +1
FreeAsInBeer
11

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

Законные причины не о «сокрытии» или организации, а о разрешении двусмысленности и специализации. Если вы реализуете два интерфейса, которые определяют «Foo», семантика для Foo может отличаться. На самом деле не лучший способ решить эту проблему, за исключением явных интерфейсов. Альтернативой является запрещение реализации перекрывающихся интерфейсов или объявление о том, что интерфейсы не могут ассоциировать семантику (что в любом случае является просто концептуальной вещью). Оба - плохие альтернативы. Иногда практичность превосходит элегантность.

Некоторые авторы / эксперты считают это существом (методы на самом деле не являются частью объектной модели типа). Но, как и все функции, где-то кому-то это нужно

Опять же, я бы не стал использовать это для сокрытия или организации. Есть способы скрыть участников от intellisense.

[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
codenheim
источник
«Есть выдающиеся авторы / эксперты, которые считают это отличительной чертой». Кто? Какова предлагаемая альтернатива? Существует по крайней мере одна проблема (т.е. специализация методов интерфейса в подынтерфейсе / классе реализации), которая потребует добавления какой-либо другой функции в C #, чтобы решить ее в отсутствие явной реализации (см. ADO.NET и универсальные коллекции). ,
jhominal
@jhominal - CLR через C # Джеффри Рихтера специально использует слово kludge. C ++ обходится без этого, программист должен явно обратиться к реализации базового метода, чтобы устранить неоднозначность или использовать приведение. Я уже упоминал, что альтернативы не очень привлекательны. Но это аспект единого наследования и интерфейсов, а не ООП в целом.
Коденхайм
Вы также могли бы упомянуть, что Java также не имеет явной реализации, несмотря на то, что у нее одинаковый дизайн «единственное наследование реализации + интерфейсы». Однако в Java возвращаемый тип переопределенного метода может быть специализированным, что устраняет часть необходимости явной реализации. (В C # было принято другое дизайнерское решение, возможно, частично из-за включения свойств).
'32
@jhominal - Конечно, когда через несколько минут я обновлю. Благодарю.
Коденхайм
Сокрытие может быть вполне уместным в ситуациях, когда класс может реализовать интерфейсный метод способом, который соответствует контракту интерфейса, но не полезен . Например, хотя мне не нравится идея классов, в которых IDisposable.Disposeчто-то происходит, но им присваивается другое имя, например Close, я считаю, что это вполне разумно для класса, в контракте которого явным образом отказывается утверждать, что код может создавать и оставлять экземпляры без вызова Disposeиспользования явной реализации интерфейса для определить IDisposable.Disposeметод, который ничего не делает.
суперкат
5

Я мог бы быть признаком грязного кода, но иногда реальный мир грязный. Одним из примеров .NET Framework является ReadOnlyColection, который скрывает мутирующий сеттер [], Add и Set.

IE ReadOnlyCollection реализует IList <T>. Смотрите также мой вопрос к SO несколько лет назад, когда я обдумывал это.

Эсбен Сков Педерсен
источник
Ну, я тоже буду использовать свой собственный интерфейс, так что нет смысла делать это неправильно с самого начала.
Кайл Баран
2
Я бы не сказал, что он скрывает мутировавшего сеттера, а скорее не дает его вообще. Лучшим примером будет то ConcurrentDictionary, что реализует различные члены IDictionary<T>явно, так что потребители ConcurrentDictionaryA) не будут вызывать их напрямую, а B) могут использовать методы, которые требуют реализации, IDictionary<T>если это абсолютно необходимо. Например, пользователям ConcurrentDictionary<T> следует звонить, TryAddа не Addизбегать ненужных исключений.
Брайан
Конечно, реализация Addявно также уменьшает беспорядок. TryAddможет делать все, что Addможет ( Addреализовано как вызов TryAdd, так что предоставление обоих будет избыточным). Однако TryAddобязательно имеет другое имя / сигнатуру, поэтому невозможно реализовать IDictionaryбез этой избыточности. Явная реализация решает эту проблему чисто.
Брайан
Хорошо с потребительской точки зрения методы скрыты.
Эсбен Сков Педерсен
1
Вы пишете о IReadonlyCollection <T>, но ссылаетесь на ReadOnlyCollection <T>. Первый в интерфейсе, второй класс. IReadonlyCollection <T> не реализует IList <T>, хотя ReadonlyCollection <T> делает.
Йорген Фог,
2

Это хорошо, когда член бессмыслен в контексте. Например, если вы создаете коллекцию только для чтения, которая реализуется IList<T>путем делегирования внутреннему объекту_wrapped тогда у вас может быть что-то вроде:

public T this[int index]
{
  get
  {
     return _wrapped[index];
  }
}
T IList<T>.this[int index]
{
  get
  {
    return this[index];
  }
  set
  {
    throw new NotSupportedException("Collection is read-only.");
  }
}
public int Count
{
  get { return _wrapped.Count; }
}
bool ICollection<T>.IsReadOnly
{
  get
  {
    return true;
  }
}

Здесь у нас есть четыре разных случая.

public T this[int index] определяется нашим классом, а не интерфейсом, и, следовательно, конечно, не является явной реализацией, хотя обратите внимание, что это действительно похоже на чтение-запись T this[int index] определенное в интерфейсе, но только для чтения.

T IList<T>.this[int index]является явным, потому что одна его часть (получатель) идеально соответствует указанному выше свойству, а другая часть всегда будет выдавать исключение. Хотя это жизненно важно для тех, кто обращается к экземпляру этого класса через интерфейс, он не имеет смысла для тех, кто использует его через переменную типа класса.

Точно так же, потому что bool ICollection<T>.IsReadOnly всегда собирается возвращать true, это совершенно бессмысленно для кода, который написан против типа класса, но может быть жизненно важным для этого, используя его через тип интерфейса, и поэтому мы реализуем его явно.

Наоборот, public int Count не реализован явно, потому что потенциально может быть полезен для кого-то, использующего экземпляр через его собственный тип.

Но с вашим «очень редко используемым» случаем я бы очень сильно склонялся к тому, чтобы не использовать явную реализацию.

В тех случаях, когда я рекомендую использовать явную реализацию, вызов метода через переменную типа класса будет либо ошибкой (попытка использовать индексированный установщик), либо бессмысленной (проверка значения, которое всегда будет одним и тем же), поэтому при сокрытии их вы защищаете пользователя от ошибочного или неоптимального кода. Это существенно отличается от кода, который, по вашему мнению, редко используется. Для этого я мог бы рассмотреть возможность использования EditorBrowsableатрибута, чтобы скрыть член от intellisense, хотя даже это было бы утомительно; У людей уже есть свое программное обеспечение для фильтрации того, что им не интересно.

Джон Ханна
источник
Если вы реализуете интерфейс, реализуйте интерфейс. В противном случае это явное нарушение принципа подстановки Лискова.
Теластин
@Telastyn интерфейс действительно реализован.
Джон Ханна
Но контракт этого не выполняется, а именно: «Это то, что представляет собой изменяемый набор произвольного доступа».
Теластин
1
@Telastyn выполняет задокументированный договор, особенно в свете «Коллекция, которая доступна только для чтения, не позволяет добавлять, удалять или изменять элементы после создания коллекции». См. Msdn.microsoft.com/en-us/library/0cfatk9t%28v=vs.110%29.aspx
Джон Ханна,
@Telastyn: если в контракте включена изменчивость, то почему интерфейс содержит IsReadOnlyсвойство?
nikie