Интерфейсы C #. Неявная реализация по сравнению с явной реализацией

Каковы различия в реализации интерфейсов неявно и явно в C #?

Когда вы должны использовать неявное и когда следует использовать явное?

Существуют ли какие-либо плюсы и / или минусы того или иного?


Официальные рекомендации Microsoft (начиная с первого издания Framework Design Guidelines ) гласят, что использование явных реализаций не рекомендуется , так как оно дает непредсказуемое поведение кода.

Я думаю, что это руководство очень актуально в период до IoC , когда вы не проходите через интерфейсы.

Может ли кто-нибудь затронуть и этот аспект?

Неявный – это когда вы определяете свой интерфейс через члена вашего classа. Явным является то, что вы определяете методы в своем classе на интерфейсе. Я знаю, что это звучит запутанно, но вот что я имею в виду: IList.CopyTo будет неявно реализован как:

 public void CopyTo(Array array, int index) { throw new NotImplementedException(); } 

и явно:

 void ICollection.CopyTo(Array array, int index) { throw new NotImplementedException(); } 

Разница заключается в том, что он неявно доступен через ваш class, который вы создали, когда он был выбран как этот class, а также когда он был включен в качестве интерфейса. Явная реализация позволяет ему быть доступным только при использовании в качестве самого интерфейса.

 MyClass myClass = new MyClass(); // Declared as concrete class myclass.CopyTo //invalid with explicit ((IList)myClass).CopyTo //valid with explicit. 

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

Я уверен, что есть больше причин использовать его / не использовать, чтобы другие публиковали.

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

Неявным определением было бы просто добавить методы / свойства и т. Д., Требуемые интерфейсом непосредственно к classу, как общедоступные методы.

Явное определение заставляет участников подвергаться воздействию только при непосредственном взаимодействии с интерфейсом, а не в основной реализации. Это предпочтительнее в большинстве случаев.

  1. Работая непосредственно с интерфейсом, вы не подтверждаете и не связываете свой код с основной реализацией.
  2. В случае, если у вас уже есть, скажем, публичное имя свойства в вашем коде, и вы хотите реализовать интерфейс, который также имеет свойство Name, делая это явно, он сохранит два отдельных. Даже если бы они делали то же самое, я бы все же делегировал явный вызов свойства Name. Вы никогда не знаете, вы можете изменить, как Name работает для обычного classа, и как позже будет работать свойство Name.
  3. Если вы реализуете интерфейс неявно, то ваш class теперь раскрывает новые формы поведения, которые могут иметь отношение только к клиенту интерфейса, а это означает, что вы недостаточно хорошо придерживаетесь своих classов (мое мнение).

В дополнение к отличным ответам, уже предоставленным, есть некоторые случаи, когда явная реализация НЕОБХОДИМА для того, чтобы компилятор смог выяснить, что требуется. Взгляните на IEnumerable в качестве яркого примера, который, скорее всего, выйдет довольно часто.

Вот пример:

 public abstract class StringList : IEnumerable { private string[] _list = new string[] {"foo", "bar", "baz"}; // ... #region IEnumerable Members public IEnumerator GetEnumerator() { foreach (string s in _list) { yield return s; } } #endregion #region IEnumerable Members IEnumerator IEnumerable.GetEnumerator() { return this.GetEnumerator(); } #endregion } 

Здесь IEnumerable реализует IEnumerable , поэтому нам тоже нужно. Но зависание, как общая, так и нормальная версия реализуют функции с той же сигнатурой метода (C # игнорирует тип возврата для этого). Это совершенно законно и прекрасно. Как компилятор разрешает использовать? Это заставляет вас иметь только самое нечеткое определение, тогда оно может решить все, что ему нужно.

то есть.

 StringList sl = new StringList(); // uses the implicit definition. IEnumerator enumerableString = sl.GetEnumerator(); // same as above, only a little more explicit. IEnumerator enumerableString2 = ((IEnumerable)sl).GetEnumerator(); // returns the same as above, but via the explicit definition IEnumerator enumerableStuff = ((IEnumerable)sl).GetEnumerator(); 

PS: Маленькая часть косвенности в явном определении для IEnumerable работает, потому что внутри функции компилятор знает, что фактический тип переменной является StringList, и именно так он разрешает вызов функции. Как ни странно, для реализации некоторых слоев абстракции некоторые из основных интерфейсов .NET, похоже, накопились.

Чтобы привести Джеффри Рихтера из CLR через C #
( EIMI означает E xplicit I nterface M ethod I mplementation)

Для вас крайне важно понять некоторые последствия, которые существуют при использовании EIMI. И из-за этих последствий вы должны стараться избегать EIMI как можно больше. К счастью, общие интерфейсы помогают вам избежать EIMI совсем немного. Но все же могут быть моменты, когда вам нужно будет их использовать (например, реализовать два метода интерфейса с тем же именем и сигнатурой). Вот большие проблемы с EIMI:

  • Нет документации, объясняющей, как тип специально реализует метод EIMI, и нет поддержки Microsoft Visual Studio IntelliSense .
  • Экземпляры типа значения помещаются в поле при переносе в интерфейс.
  • EIMI не может быть вызван производным типом.

Если вы используете ссылку на интерфейс, любая виртуальная цепочка может быть явно заменена EIMI на любом производном classе и когда объект такого типа передается на интерфейс, ваша виртуальная цепочка игнорируется и вызывается явная реализация. Это ничего, кроме polymorphismа.

EIMI также могут использоваться для скрытия нежестко типизированных членов интерфейса из реализаций базовых интерфейсов Framework, таких как IEnumerable , поэтому ваш class не выдает строго не типизированный метод напрямую, а является синтаксически правильным.

Причина № 1

Я склонен использовать явную реализацию интерфейса, когда хочу препятствовать «программированию для реализации» (« Принципы проектирования из шаблонов проектирования» ).

Например, в веб-приложении на основе MVP :

 public interface INavigator { void Redirect(string url); } public sealed class StandardNavigator : INavigator { void INavigator.Redirect(string url) { Response.Redirect(url); } } 

Теперь другой class (например, ведущий ) с меньшей вероятностью будет зависеть от реализации StandardNavigator и, скорее всего, будет зависеть от интерфейса INavigator (так как реализация должна быть применена к интерфейсу для использования метода Redirect).

Причина № 2

Еще одна причина, по которой я могу пойти с явной реализацией интерфейса, – это сохранить очищающий интерфейс classа по умолчанию. Например, если я разрабатываю серверный элемент управления ASP.NET , возможно, мне нужны два интерфейса:

  1. Основной интерфейс classа, который используется разработчиками веб-страниц; а также
  2. «Скрытый» интерфейс, используемый ведущим, который я разрабатываю для обработки логики управления

Далее следует простой пример. Это элемент управления со списком, в котором перечислены клиенты. В этом примере разработчик веб-страницы не заинтересован в заполнении списка; вместо этого они просто хотят иметь возможность выбирать клиента по GUID или получать GUID выбранного клиента. Ведущий будет заполнять окно при загрузке первой страницы, и этот ведущий инкапсулируется элементом управления.

 public sealed class CustomerComboBox : ComboBox, ICustomerComboBox { private readonly CustomerComboBoxPresenter presenter; public CustomerComboBox() { presenter = new CustomerComboBoxPresenter(this); } protected override void OnLoad() { if (!Page.IsPostBack) presenter.HandleFirstLoad(); } // Primary interface used by web page developers public Guid ClientId { get { return new Guid(SelectedItem.Value); } set { SelectedItem.Value = value.ToString(); } } // "Hidden" interface used by presenter IEnumerable ICustomerComboBox.DataSource { set; } } 

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

Но это не серебряный пушечный ящик

Я бы не рекомендовал всегда использовать явные реализации интерфейса. Это всего лишь два примера, где они могут быть полезными.

В дополнение к другим причинам, уже заявленным, это та ситуация, в которой class реализует два разных интерфейса, которые имеют свойство / метод с тем же именем и сигнатурой.

 ///  /// This is a Book ///  interface IBook { string Title { get; } string ISBN { get; } } ///  /// This is a Person ///  interface IPerson { string Title { get; } string Forename { get; } string Surname { get; } } ///  /// This is some freaky book-person. ///  class Class1 : IBook, IPerson { ///  /// This method is shared by both Book and Person ///  public string Title { get { string personTitle = "Mr"; string bookTitle = "The Hitchhikers Guide to the Galaxy"; // What do we do here? return null; } } #region IPerson Members public string Forename { get { return "Lee"; } } public string Surname { get { return "Oades"; } } #endregion #region IBook Members public string ISBN { get { return "1-904048-46-3"; } } #endregion } 

Этот код компилируется и работает нормально, но свойство Title является общим.

Ясно, что мы хотели бы, чтобы значение Title Return зависело от того, обрабатывали ли мы Class1 как книгу или Person. Это когда мы можем использовать явный интерфейс.

 string IBook.Title { get { return "The Hitchhikers Guide to the Galaxy"; } } string IPerson.Title { get { return "Mr"; } } public string Title { get { return "Still shared"; } } 

Обратите внимание, что явные определения интерфейсов выводятся как Public – и, следовательно, вы не можете объявлять их публичными (или иначе) явно.

Обратите также внимание на то, что вы все еще можете иметь «общую» версию (как показано выше), но, хотя это возможно, существование такого свойства сомнительно. Возможно, его можно было бы использовать в качестве реализации Title Title по умолчанию, так что существующий код не нужно было бы изменять, чтобы отличать Class1 с IBook или IPerson.

Если вы не определяете «разделяемое» (неявное) название, потребители Class1 должны явно бросать экземпляры Class1 в IBook или IPerson сначала, иначе код не будет компилироваться.

Я использую явную реализацию интерфейса большую часть времени. Вот основные причины.

Рефакторинг более безопасен

При изменении интерфейса лучше, если компилятор может проверить это. Это сложнее с неявными реализациями.

Приходят два распространенных случая:

  • Добавление функции в интерфейс, где уже существует class, реализующий этот интерфейс, имеет метод с той же сигнатурой, что и новый . Это может привести к неожиданному поведению и несколько раз укусил меня. Трудно «видеть» при отладке, поскольку эта функция, вероятно, не находится с другими методами интерфейса в файле (проблема самодокументирования, упомянутая ниже).

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

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

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

Это самодокументирующее

Если я вижу «public bool Execute ()» в classе, потребуется дополнительная работа, чтобы понять, что это часть интерфейса. Кто-то, вероятно, должен будет прокомментировать это, сказав это, или поместив его в группу других реализаций интерфейса, все под регионом или группировкой комментариев, говорящих «реализация ITask». Конечно, это работает только в том случае, если заголовок группы не выключен.

Принимая во внимание, что: «bool ITask.Execute ()» ясен и недвусмыслен.

Четкое разделение реализации интерфейса

Я считаю, что интерфейсы являются более «публичными», чем общедоступными, потому что они созданы для того, чтобы выставлять только часть поверхности конкретного типа. Они уменьшают тип до способности, поведения, набора признаков и т. Д. И в реализации я считаю полезным сохранить это разделение.

Когда я просматриваю код classа, когда я сталкиваюсь с явными реализациями интерфейса, мой мозг переходит в режим «кодового контракта». Часто эти реализации просто переходят к другим методам, но иногда они выполняют дополнительную проверку состояния / параметра, преобразование входящих параметров для лучшего соответствия внутренним требованиям или даже перевод для целей управления версиями (т. Е. Несколько поколений интерфейсов, которые полностью соответствуют общим реализациям).

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

Связанный: Причина 2 выше Джона .

И так далее

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

  • При необходимости, в зависимости от значения или необходимости внутреннего интерфейса
  • Отказывает «программирование на реализацию» ( причина 1 Джона )

Проблемы

Это не все весело и весело. Есть некоторые случаи, когда я придерживаюсь имплицитов:

  • Типы значений, потому что для этого потребуется бокс и более низкий уровень. Это не строгое правило и зависит от интерфейса и того, как он предназначен для использования. IComparable? Неявные. IFormattable? Вероятно, явный.
  • Тривиальные системные интерфейсы, которые имеют методы, которые часто вызываются напрямую (например, IDisposable.Dispose).

Кроме того, может быть больно делать кастинг, когда у вас действительно есть конкретный тип и вы хотите вызвать явный метод интерфейса. Я имею дело с этим одним из двух способов:

  1. Добавьте публикацию и предложите им методы интерфейса для реализации. Обычно это происходит при использовании более простых интерфейсов при работе внутри системы.
  2. (Мой предпочтительный метод) Добавить public IMyInterface I { get { return this; } } public IMyInterface I { get { return this; } } (который должен быть вставлен) и вызвать foo.I.InterfaceMethod() . Если несколько интерфейсов, которые нуждаются в этой способности, расширьте имя за пределами I (по моему опыту, редко бывает, что у меня есть эта потребность).

Если вы реализуете явно, вы сможете ссылаться только на элементы интерфейса через ссылку, относящуюся к типу интерфейса. Ссылка, являющаяся типом реализующего classа, не будет выставлять эти элементы интерфейса.

Если ваш class реализации не является общедоступным, за исключением метода, используемого для создания classа (который может быть фабричным или контейнером IoC ), и за исключением методов интерфейса (конечно), то я не вижу никакого преимущества для явной реализации интерфейсы.

В противном случае явное внедрение интерфейсов гарантирует, что ссылки на ваш конкретный class реализации не будут использоваться, что позволит вам впоследствии изменить эту реализацию. «Совершенно верно», я полагаю, является «преимуществом». Хорошо реализованная реализация может выполнить это без явной реализации.

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

Как и многие другие, преимущество – недостаток (и наоборот). Явное внедрение интерфейсов гарантирует, что ваш конкретный код реализации classа не будет показан.

Неявная реализация интерфейса – это то, где у вас есть метод с той же сигнатурой интерфейса.

Явная реализация интерфейса – это то, где вы явно указываете, к какому интерфейсу относится этот метод.

 interface I1 { void implicitExample(); } interface I2 { void explicitExample(); } class C : I1, I2 { void implicitExample() { Console.WriteLine("I1.implicitExample()"); } void I2.explicitExample() { Console.WriteLine("I2.explicitExample()"); } } 

MSDN: неявные и явные реализации интерфейса

Каждый член classа, который реализует интерфейс, экспортирует объявление, которое семантически похоже на то, как написаны декларации интерфейса VB.NET, например

 Public Overridable Function Foo() As Integer Implements IFoo.Foo 

Хотя имя члена classа часто совпадает с именем члена интерфейса, и член classа часто будет публичным, ни одна из этих вещей не требуется. Можно также заявить:

 Protected Overridable Function IFoo_Foo() As Integer Implements IFoo.Foo 

В этом случае classу и его производным будет разрешен доступ к члену classа с использованием имени IFoo_Foo , но внешний мир сможет получить доступ к этому конкретному члену, IFoo в IFoo . Такой подход часто бывает хорош в тех случаях, когда интерфейсный метод будет указывать поведение во всех реализациях, но полезное поведение только для некоторых [например, указанное поведение для метода IList.Add ) – это бросить NotSupportedException ]. К сожалению, единственный правильный способ реализации интерфейса в C #:

 int IFoo.Foo() { return IFoo_Foo(); } protected virtual int IFoo_Foo() { ... real code goes here ... } 

Не так хорошо.

Одно важное использование явной реализации интерфейса – это когда требуется реализовать интерфейсы со смешанной видимостью .

Проблема и решение хорошо объяснены в статье C # Internal Interface .

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

  • Когда следует использовать интерфейс в java?
  • Когда мне нужно использовать интерфейсы вместо абстрактных classов?
  • Множественное наследование в C #
  • Какие интерфейсы реализуют все массивы на C #?
  • Что означает «программа для интерфейсов, а не реализации»?
  • Реализация интерфейса через Reflection
  • Метод передачи Java в качестве параметра
  • Найти Java-classы, реализующие интерфейс
  • Явное вызов метода по умолчанию в Java
  • Интерфейсы C # - В чем смысл?
  • Интерфейс против абстрактного classа (общий OO)
  • Давайте будем гением компьютера.