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

StackOverflow https://stackoverflow.com/questions/143405

  •  02-07-2019
  •  | 
  •  

Вопрос

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

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

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


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

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

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

Это было полезно?

Решение

Неявный это когда вы определяете свой интерфейс через элемент в своем классе. Явный это когда вы определяете методы внутри своего класса в интерфейсе.Я знаю, это звучит запутанно, но вот что я имею в виду: 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.

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

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

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

Другие советы

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

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

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

В дополнение к уже предоставленным отличным ответам, есть некоторые случаи, когда требуется явная реализация, чтобы компилятор мог определить, что требуется.Взгляните на 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 , и именно так он разрешает вызов функции.Изящный маленький факт для реализации некоторых слоев абстракции часть .Чистые базовые интерфейсы, похоже, накопилось.

Причина №1

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

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

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

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

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

Причина №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; }
}

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

Но Это не Серебряное Пушечное Ядро

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

Процитирую Джеффри Рихтера из CLR через C#
(ЭЙМИ означает Eзапрашивать Явнутренняя поверхность Mэтод Яреализация)

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

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

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

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

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

/// <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"; }
}

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

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

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

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

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

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

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

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

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

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

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

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

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

Принимая во внимание , что:'bool ITask.Выполнить()' - это ясно и недвусмысленно.

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

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

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

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

Похожие: Причина 2 , приведенная выше Джоном.

И так далее

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

Проблемы

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

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

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

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

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

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

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

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

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

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

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

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

Каждый член класса, реализующий интерфейс, экспортирует объявление, семантически аналогичное способу написания объявлений интерфейса 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 ... }

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top