Чи дозволено використовувати явну реалізацію інтерфейсу, щоб приховати членів у 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
Я працював з хлопцем, який робив це регулярно. Це зводило мене з абсолютно горіхів.
Джоель Етертон

... і почніть використовувати агрегацію.
mikolai

Відповіді:


39

Так, це взагалі погана форма.

Таким чином, я вважав, що має сенс сховати їх від класу впровадження шляхом явної реалізації, щоб уникнути безладу IntelliSense.

Якщо ваш IntelliSense занадто захаращений, ваш клас занадто великий. Якщо ваш клас занадто великий, він, ймовірно, робить занадто багато речей і / або погано розроблений.

Виправте свою проблему, а не ваш симптом.


Чи доцільно використовувати його для видалення попереджень компілятора про невикористані члени?
Гусдор

11
"Виправте свою проблему, а не ваш симптом." +1
FreeAsInBeer

11

Використовуйте функцію для того, для чого вона призначена. Зменшення захаращення простору імен - не одне з таких.

Законні причини не полягають у тому, щоб "ховатися" чи організуватись, вони повинні вирішити неоднозначність та спеціалізуватися. Якщо ви реалізуєте два інтерфейси, які визначають "Foo", семантика для Foo може відрізнятися. Дійсно, не кращий спосіб вирішити це питання, крім явних інтерфейсів. Альтернатива - забороняти реалізовувати інтерфейси, що перекриваються, або заявляти, що інтерфейси не можуть пов'язувати семантику (що все одно просто концептуальна річ). Обидва є поганими альтернативами. Іноді практичність козиряє елегантністю.

Деякі автори / експерти вважають це зграєю функції (методи насправді не є частиною об'єктної моделі типу). Але як і всі функції, десь комусь це потрібно.

Знову ж таки, я б не використовував це для приховування чи організації. Є способи приховати членів від інтелігенції.

[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]

"Є видатні автори / експерти, які вважають це зграєю функції". Хто є? Яка запропонована альтернатива? Існує щонайменше одна проблема (тобто спеціалізація методів інтерфейсу в підінтерфейсі / класі реалізації), яка потребує додавання до C # іншої функції, щоб вирішити її за відсутності явної реалізації (див. ADO.NET та загальні колекції) .
Джомінал

@jhominal - CLR через C # Джефрі Ріхтер спеціально використовує слово kludge. C ++ обходиться без цього, програміст повинен явно посилатися на реалізацію базового методу, щоб відключити або використовувати кастинг. Я вже згадував, що альтернативи не дуже привабливі. Але це аспект єдиного успадкування та інтерфейсів, а не ООП взагалі.
codenheim

Ви також могли зазначити, що Java також не має явної реалізації, незважаючи на те, що дизайн "єдиного успадкування реалізації + інтерфейси" не має однакового. Однак у Java тип повернення методу, що переосмислюється, може бути спеціалізованим, уникаючи частини потреби в явній реалізації. (У C # було прийнято інше дизайнерське рішення, ймовірно, частково через включення властивостей).
Джомінал

@jhominal - Звичайно, коли через кілька хвилин я оновлюсь. Спасибі.
codenheim

Сховати може бути цілком доречно в ситуаціях, коли клас може реалізувати метод інтерфейсу таким чином, що відповідає договору інтерфейсу, але не є корисним . Наприклад, хоча мені не подобається ідея класів, де IDisposable.Disposeщось робить, але дається інша назва, як-от Close, я вважаю цілком розумним для класу, контракт якого прямо відмовляється заявляти, що код може створювати та відмовлятися від екземплярів, не вимагаючи Disposeвикористовувати явну реалізацію інтерфейсу для визначити IDisposable.Disposeметод, який нічого не робить.
supercat

5

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

IE ReadOnlyCollection реалізує IList <T>. Дивіться також моє запитання до ТА кілька років тому, коли я розмірковував над цим.


Що ж, це мій власний інтерфейс, який я також буду використовувати, тому немає сенсу робити це неправильно з самого початку.
Кайл Баран

2
Я б не сказав, що він приховує мутуючий сетер, а швидше, що він його зовсім не забезпечує. Кращим прикладом може бути те ConcurrentDictionary, що реалізує різні члени IDictionary<T>явно, щоб споживачі ConcurrentDictionaryА) не називали їх безпосередньо, а Б) могли використовувати методи, які вимагають впровадження, IDictionary<T>якщо це абсолютно необхідно. Наприклад, користувачі ConcurrentDictionary<T> повинні телефонувати, TryAddа не Addуникати зайвих винятків.
Брайан

Звичайно, Addявна реалізація також зменшує безлад. TryAddможе робити що завгодно Add( Addреалізується як заклик до TryAdd, тому надання обох було б зайвим). Однак TryAddобов'язково має інше ім’я / підпис, тому неможливо реалізувати IDictionaryбез цього надмірності. Явна реалізація вирішує цю проблему чисто.
Брайан

Ну і з точки зору споживача методи приховані.
Есбен Сков Педерсен

1
Ви пишете про IReadonlyCollection <T>, але посилаєтесь на ReadOnlyCollection <T>. Перший в інтерфейсі, другий - клас. IReadonlyCollection <T> не реалізує IList <T>, навіть якщо ReadonlyCollection <T> робить.
Jørgen Fogh

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атрибутом, щоб приховати член від інтелігенції, хоча навіть від цього я б втомлювався; мозок людей вже має власне програмне забезпечення для фільтрації того, що їх не цікавить.


Якщо ви реалізуєте інтерфейс, застосуйте його. В іншому випадку це явне порушення принципу заміни Ліскова.
Теластин

@Telastyn інтерфейс дійсно реалізований.
Джон Ханна

Але контрактом його не влаштовують - конкретно, "Це щось, що являє собою змінну колекцію випадкового доступу".
Теластин

1
@Telastyn виконує задокументований контракт, особливо в світлі "Колекція, яка читається лише не дозволяє додавати, видаляти або змінювати елементи після створення колекції". Дивіться msdn.microsoft.com/en-us/library/0cfatk9t%28v=vs.110%29.aspx
Джон Ханна

@Telastyn: Якщо контракт включав зміни, то чому б інтерфейс містив IsReadOnlyвластивість?
nikie
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.