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

571

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

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

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


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

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

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

  • 0
    Читайте полную статью об интерфейсах C #: planetofcoders.com/c-interfaces
  • 0
    Да, явные интерфейсы следует избегать и более профессиональный подход будет реализовать ISP (принцип сегрегации интерфейс) здесь подробно статья на том же codeproject.com/Articles/1000374/...
Теги:
interface

11 ответов

426
Лучший ответ

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

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

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

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

  • 6
    Я знаю, что этот пост старый, но я нашел его очень полезным - одна вещь, на которую следует обратить внимание, если она не ясна, потому что это было не для меня, это то, что в примере неявный имеет ключевое слово public ... в противном случае вы получите ошибка
  • 0
    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); // ошибка времени компиляции}
Показать ещё 3 комментария
189

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

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

  • Работая непосредственно с интерфейсом, вы не подтверждаете, и связывание кода с основной реализацией.
  • Если у вас уже есть, скажем, публичное свойство Name в ваш код, и вы хотите реализовать интерфейс, который также имеет Свойство Name, делая это явно, будет держать два отдельных. Даже если бы они делали то же самое, я бы все же делегировал явное вызовите свойство Name. Вы никогда не знаете, вы можете изменить как имя работает для обычного класса и как имя, интерфейс свойство работает позже.
  • Если вы реализуете интерфейс неявно, то ваш класс теперь выставляет новых поведений, которые могут иметь отношение только к клиенту интерфейс, и это означает, что вы не держите свои классы краткими достаточно (мое мнение).
  • 2
    Вы делаете некоторые хорошие моменты здесь. особенно А. Я обычно пропускаю свои уроки как интерфейс в любом случае, но я никогда не думал об этом с этой точки зрения.
  • 3
    Я не уверен, что согласен с пунктом C. Объект Cat может реализовывать IEatable, но Eat () является основной частью. Могут быть случаи, когда вы захотите просто вызвать Eat () для Cat, когда вы используете «сырой» объект, а не через интерфейс IEatable, нет?
Показать ещё 4 комментария
66

В дополнение к отличным ответам, уже предоставленным, есть некоторые случаи, когда явная реализация НЕОБХОДИМА для того, чтобы компилятор смог выяснить, что требуется. Взгляните на 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, поэтому нам тоже нужно. Но зависание, как общая, так и нормальная версия реализуют функции с одной и той же сигнатурой метода (С# игнорирует тип возврата для этого). Это совершенно законно и прекрасно. Как компилятор разрешает использовать? Это заставляет вас иметь только самое нечеткое определение, тогда оно может решить все, что ему нужно.

т.

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.

  • 3
    почему твой класс абстрактный
  • 5
    @Tassadaque: Через 2 года все, что я могу сказать, это «Хороший вопрос». Я понятия не имею, разве что, возможно, я скопировал этот код из того, над чем работал, где он был abstract .
Показать ещё 2 комментария
31

Процитировать Джеффри Рихтера от CLR через С#
( EIMI означает E xplicit I nterface M этад I реализация)

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

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

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

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

  • 2
    Реализация интерфейсов, хотя и легальная, в лучшем случае сомнительна. Явные реализации, как правило, должны связываться с виртуальным методом либо напрямую, либо с помощью логики обтекания, которая должна связываться с производными классами. Хотя, безусловно, возможно использовать интерфейсы способами, которые враждебны соответствующим соглашениям ООП, это не означает, что их нельзя использовать лучше.
  • 3
    @Valentin Что означают EIMI и IEMI?
Показать ещё 4 комментария
30

Причина № 1

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

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

Причина № 2

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

  • Основной интерфейс класса, который используется разработчиками веб-страниц; и
  • "скрытый" интерфейс, используемый ведущим, который я разрабатываю для обработки логики управления.

Далее следует простой пример. Это элемент управления со списком, в котором перечислены клиенты. В этом примере разработчик веб-страницы не заинтересован в заполнении списка; вместо этого они просто хотят иметь возможность выбрать клиента по 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; }
}

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

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

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

17

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

/// <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 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 - так что существующий код не нужно будет изменять, чтобы отличать Class1 с IBook или IPerson.

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

11

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

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

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

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

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

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

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

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

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

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

В то время как: bool ITask.Execute() 'ясен и недвусмыслен.

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

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

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

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

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

И так далее

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

Проблемы

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

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

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

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

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

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

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

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

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

  • 1
    Хороший ответ, Билл. Другие ответы были великолепны, но вы предоставили некоторые дополнительные объективные точки зрения (помимо ваших мнений), которые облегчили мне понимание. Как и у большинства вещей, есть плюсы и минусы с неявными или явными реализациями, поэтому вам просто нужно использовать лучший вариант для вашего конкретного сценария или варианта использования. Я бы сказал, что те, кто пытается лучше понять это, получат пользу от прочтения вашего ответа.
6

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

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

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

5

Каждый член класса, который реализует интерфейс, экспортирует объявление, которое семантически похоже на то, как написаны декларации интерфейса 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]. К сожалению, единственный правильный способ реализации интерфейса в С#:

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

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

1

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

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

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню