Я использую явную реализацию интерфейса большую часть времени. Вот основные причины.
Рефакторинг безопаснее
При изменении интерфейса лучше, если компилятор может это проверить. Это сложнее с неявными реализациями.
На ум приходят два распространенных случая:
Добавление функции в интерфейс, где уже существует класс, реализующий этот интерфейс, с методом с той же сигнатурой, что и у нового . Это может привести к неожиданному поведению, и укусил меня несколько раз. Трудно "увидеть" при отладке, потому что эта функция, вероятно, не находится с другими методами интерфейса в файле (проблема самодокументирования, упомянутая ниже).
Удаление функции из интерфейса . Неявно реализованные методы будут внезапно мертвым кодом, но явно реализованные методы будут захвачены ошибкой компиляции. Даже если мертвый код хорошо хранить, я хочу, чтобы его просмотрели и продвигали.
К сожалению, в C # нет ключевого слова, которое заставляет нас помечать метод как неявную реализацию, поэтому компилятор может выполнять дополнительные проверки. Виртуальные методы не имеют ни одной из вышеперечисленных проблем из-за обязательного использования 'override' и 'new'.
Примечание: для фиксированных или редко меняющихся интерфейсов (как правило, из API поставщика) это не проблема. Однако для моих собственных интерфейсов я не могу предсказать, когда и как они изменятся.
Это самодокументирование
Если я увижу public bool Execute () в классе, потребуется дополнительная работа, чтобы выяснить, что это часть интерфейса. Кто-то, вероятно, должен будет прокомментировать это, говоря об этом, или поместить его в группу других реализаций интерфейса, все в рамках региона или группового комментария, говорящего «реализация ITask». Конечно, это работает, только если заголовок группы не за кадром.
Принимая во внимание, что 'bool ITask.Execute ()' понятен и однозначен.
Четкое разделение реализации интерфейса
Я думаю, что интерфейсы являются более «общедоступными», чем общедоступными методами, потому что они созданы так, чтобы показать лишь небольшую часть поверхности конкретного типа. Они сводят тип к возможности, поведению, набору признаков и т. Д. И в реализации, я думаю, полезно сохранить это разделение.
Когда я просматриваю код класса, когда сталкиваюсь с явными реализациями интерфейса, мой мозг переключается в режим «контракта кода». Часто эти реализации просто перенаправляют на другие методы, но иногда они будут выполнять дополнительную проверку состояния / параметра, преобразование входящих параметров для лучшего соответствия внутренним требованиям или даже перевод для целей управления версиями (то есть несколько поколений интерфейсов, все переходящие к общим реализациям).
(Я понимаю, что public также являются контрактами на код, но интерфейсы намного сильнее, особенно в кодовой базе, управляемой интерфейсом, где прямое использование конкретных типов обычно является признаком кода, предназначенного только для внутреннего использования.)
Связанный: Причина 2 выше Джон .
И так далее
Плюс преимущества, уже упомянутые в других ответах здесь:
Проблемы
Это не все веселье и счастье. В некоторых случаях я придерживаюсь следствий:
- Типы значений, потому что это потребует бокса и снижения производительности. Это не строгое правило, оно зависит от интерфейса и способа его использования. IComparable? Неявные. IFormattable? Вероятно, явно.
- Тривиальные системные интерфейсы, которые имеют методы, которые часто вызываются напрямую (например, IDisposable.Dispose).
Кроме того, может быть затруднительно выполнять приведение, когда у вас действительно есть конкретный тип и вы хотите вызвать явный интерфейсный метод. Я имею дело с этим одним из двух способов:
- Добавьте public и передайте им методы интерфейса для реализации. Обычно это происходит с более простыми интерфейсами при внутренней работе.
- (Мой предпочтительный метод) Добавьте
public IMyInterface I { get { return this; } }
(который должен быть встроен) и вызовите foo.I.InterfaceMethod()
. Если несколько интерфейсов нуждаются в этой способности, расширьте имя за пределы I (по моему опыту, это редко случается).