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

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

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

Есть ли плюсы и / или минусы одного или другого?


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

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

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

27.09.2008 10:56:16
Читать статью полностью об интерфейсах C #: planetofcoders.com/c-interfaces
Gaurav Agrawal 17.05.2012 09:50:09
Да, явных интерфейсов следует избегать, и для реализации ISP (принцип разделения интерфейса) следует использовать более профессиональный подход. Вот подробная статья о том же codeproject.com/Articles/1000374/…
Shivprasad Koirala 15.06.2015 03:31:07
12 ОТВЕТОВ
РЕШЕНИЕ

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

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

и явно как:

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

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

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

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

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

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

489
25.11.2019 05:13:06
Я знаю, что этот пост старый, но я нашел его очень полезным - одна вещь, на которую следует обратить внимание, если она не ясна, потому что это было не для меня, это то, что в примере у неявного есть publicключевое слово ... в противном случае вы получите ошибку
jharr100 18.11.2014 19:30:13
CLR Джеффри Рихтера через C # 4 ed ch 13 показывает пример, где приведение не требуется: внутренняя структура SomeValueType: IComparable {private Int32 m_x; public SomeValueType (Int32 x) {m_x = x; } public Int32 CompareTo (SomeValueType other) {...);} Int32 IComparable.CompareTo (Object other) {return CompareTo ((SomeValueType) other); }} public static void Main () {SomeValueType v = new SomeValueType (0); Объект o = новый объект (); Int32 n = v.CompareTo (v); // Нет бокса n = v.CompareTo (o); // ошибка времени компиляции}
Andy Dent 26.08.2015 05:18:39
Имеет ли смысл в любом случае иметь обе реализации, неявную и явную?
Raskolnikov 17.12.2015 12:20:26
Сегодня я столкнулся с редкой ситуацией, в которой ТРЕБУЕТСЯ использовать явный интерфейс: класс с полем, сгенерированным конструктором интерфейса, который создает поле как личное (Xamarin предназначается для iOS, используя раскадровку iOS). И интерфейс, где имеет смысл выставить это поле (общедоступный только для чтения). Я мог бы изменить имя геттера в интерфейсе, но существующее имя было наиболее логичным для объекта. Так что вместо этого я сделал явную реализацию со ссылкой на частное поле: UISwitch IScoreRegPlayerViewCell.markerSwitch { get { return markerSwitch; } }.
ToolmakerSteve 12.07.2016 23:11:55
Ужасы программирования. Ну, красиво развенчан!
Liquid Core 3.05.2018 11:54:34

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

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

  1. Работая напрямую с интерфейсом, вы не подтверждаете и связываете свой код с базовой реализацией.
  2. Если в вашем коде уже есть, скажем, открытое свойство Name, и вы хотите реализовать интерфейс, который также имеет свойство Name, то если вы сделаете это явно, эти два элемента будут разделены. Даже если бы они делали то же самое, я бы все же делегировал явный вызов свойству Name. Вы никогда не знаете, вы можете захотеть изменить то, как Name работает для нормального класса и как Name, свойство интерфейса работает позже.
  3. Если вы реализуете интерфейс неявно, то ваш класс теперь демонстрирует новые поведения, которые могут иметь отношение только к клиенту интерфейса, и это означает, что вы недостаточно кратко сохраняете свои классы (мое мнение).
201
4.12.2017 18:32:55
Вы делаете некоторые хорошие моменты здесь. особенно А. Я обычно пропускаю свои уроки как интерфейс в любом случае, но я никогда не думал об этом с этой точки зрения.
mattlant 27.09.2008 11:15:19
Я не уверен, что согласен с пунктом C. Объект Cat может реализовывать IEatable, но Eat () является основной частью. Могут быть случаи, когда вы захотите просто вызвать Eat () для Cat, когда вы используете «сырой» объект, а не через интерфейс IEatable, нет?
LegendLength 6.05.2009 12:50:02
Я знаю некоторые места, где Catдействительно IEatableнет возражений.
Humberto 14.06.2010 01:37:16
Я полностью не согласен со всем вышеперечисленным и скажу, что использование явных интерфейсов является рецептом катастрофы, а не ООП или ООД по их определению (см. Мой ответ о полиморфизме)
Valentin Kuzub 9.02.2011 18:49:14
Смотрите также: stackoverflow.com/questions/4103300/…
Zack Jannsen 18.09.2012 13:13:10

В дополнение к уже предоставленным отличным ответам, в некоторых случаях ТРЕБУЕТСЯ явная реализация, чтобы компилятор мог выяснить, что требуется. Взгляните на IEnumerable<T>основной пример, который, скорее всего, встречается довольно часто.

Вот пример:

public abstract class StringList : IEnumerable<string>
{
    private string[] _list = new string[] {"foo", "bar", "baz"};

    // ...

    #region IEnumerable<string> Members
    public IEnumerator<string> GetEnumerator()
    {
        foreach (string s in _list)
        { yield return s; }
    }
    #endregion

    #region IEnumerable Members
    IEnumerator IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }
    #endregion
}

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

то есть.

StringList sl = new StringList();

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

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

65
7.07.2014 11:09:44
@Tassadaque: Через 2 года все, что я могу сказать, это «Хороший вопрос». Я понятия не имею, разве что я скопировал этот код из того, над чем работал abstract.
Matthew Scharley 26.06.2011 22:17:27
@Tassadaque, вы правы, но я думаю, что смысл поста Мэтью Шарли выше не потерян.
funkymushroom 7.07.2016 17:32:49

По словам Джеффри Рихтера из CLR с помощью C #
( EIMI означает Е Xplicit Я nterface М еню Я mplementation)

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

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

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

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

34
23.05.2016 17:22:41
Реализация интерфейсов, хотя и легальная, в лучшем случае сомнительна. Явные реализации, как правило, должны связываться с виртуальным методом либо напрямую, либо с помощью логики обтекания, которая должна связываться с производными классами. Хотя, безусловно, возможно использовать интерфейсы способами, которые враждебны соответствующим соглашениям ООП, это не означает, что их нельзя использовать лучше.
supercat 30.05.2014 20:10:26
@Valentin Что означают EIMI и IEMI?
Dzienny 9.06.2014 12:50:14
Явная реализация метода интерфейса
scobi 15.08.2014 07:32:10
-1 для «Как правило, я вижу интерфейсы как полупериодическую (в лучшем случае) функцию ООП, она обеспечивает наследование, но не обеспечивает настоящий полиморфизм». Я категорически не согласен. Наоборот, все интерфейсы - это полиморфизм, а не наследование. Они приписывают несколько классификаций к типу. Избегайте IEMI, когда можете, и делегируйте, как подсказал @supercat, когда не можете. Не избегайте интерфейсов.
Aluan Haddad 4.02.2016 03:17:19
«EIMI не может быть вызван производным типом». << ЧТО? Это не правда. Если я явно реализую интерфейс для типа, то я наследую от этого типа, я все еще могу привести его к интерфейсу для вызова метода, точно так же, как и для типа, для которого он реализован. Так что не уверен, о чем ты говоришь. Даже внутри производного типа я могу просто привести «this» к рассматриваемому интерфейсу, чтобы получить явно реализованный метод.
Triynko 31.10.2018 21:09:08

Причина № 1

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

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

public interface INavigator {
    void Redirect(string url);
}

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

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

Причина № 2

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

  1. Основной интерфейс класса, который используется разработчиками веб-страниц; и
  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<CustomerDto> ICustomerComboBox.DataSource { set; }
}

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

Но это не серебряное пушечное ядро

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

33
23.05.2016 17:17:00

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

/// <summary>
/// This is a Book
/// </summary>
interface IBook
{
    string Title { get; }
    string ISBN { get; }
}

/// <summary>
/// This is a Person
/// </summary>
interface IPerson
{
    string Title { get; }
    string Forename { get; }
    string Surname { get; }
}

/// <summary>
/// This is some freaky book-person.
/// </summary>
class Class1 : IBook, IPerson
{
    /// <summary>
    /// This method is shared by both Book and Person
    /// </summary>
    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 зависело от того, относились ли мы к Class1 как к Книге или к Человеку. Это когда мы можем использовать явный интерфейс.

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 - и, следовательно, вы не можете явно объявить их как public (или иным образом).

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

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

19
23.05.2016 17:12:57

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

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

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

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

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

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

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

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

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

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

Принимая во внимание, что 'bool ITask.Execute ()' понятен и однозначен.

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

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

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

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

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

И так далее

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

Проблемы

Это не все веселье и счастье. В некоторых случаях я придерживаюсь следствий:

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

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

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

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

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

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

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

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

8
17.07.2016 18:28:25
Хороший ответ, Билл. Другие ответы были великолепны, но вы предоставили некоторые дополнительные объективные точки зрения (помимо ваших мнений), которые облегчили мне понимание. Как и у большинства вещей, есть плюсы и минусы с неявными или явными реализациями, поэтому вам просто нужно использовать лучший вариант для вашего конкретного сценария или варианта использования. Я бы сказал, что те, кто пытается лучше понять это, получат пользу от прочтения вашего ответа.
Daniel Eagle 2.12.2014 18:21:44

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

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

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: неявные и явные реализации интерфейса

6
26.05.2011 14:12:52

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

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

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

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

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

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

Не так приятно.

6
23.05.2016 17:29:12

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

Проблема и решение хорошо объяснены в статье C # Внутренний интерфейс .

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

2
23.05.2016 17:27:47

Предыдущие ответы объясняют, почему реализация интерфейса явно в C # может быть предпочтительнее (в основном по формальным причинам). Однако есть одна ситуация, где явная реализация обязательна : во избежание утечки инкапсуляции, когда интерфейс не является public, а реализующий класс - public.

// Given:
internal interface I { void M(); }

// Then explicit implementation correctly observes encapsulation of I:
// Both ((I)CExplicit).M and CExplicit.M are accessible only internally.
public class CExplicit: I { void I.M() { } }

// However, implicit implementation breaks encapsulation of I, because
// ((I)CImplicit).M is only accessible internally, while CImplicit.M is accessible publicly.
public class CImplicit: I { public void M() { } }

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

Неявная реализация интерфейса в C # - большое удобство. На практике многие программисты используют его постоянно / везде без дальнейшего рассмотрения. Это приводит к грязным поверхностям в лучшем случае и утечке инкапсуляции в худшем случае. Другие языки, такие как F #, даже не позволяют этого .

2
16.10.2019 11:39:21