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

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

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

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


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

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

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

Ответ 1

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

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

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

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

Ответ 2

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

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

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

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

т.

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.

Ответ 4

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

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

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

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

Ответ 5

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

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

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

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

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

Ответ 6

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

/// <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 в первую очередь, иначе код не будет компилироваться.

Ответ 7

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

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

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

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

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

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

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

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

Самодокументируемый

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

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

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

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

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

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

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

И так далее

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

Проблемы

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

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

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

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

Ответ 8

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

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

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

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

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

Ответ 9

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

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

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

Ответ 10

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

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

Ответ 11

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

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

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

Ответ 12

Предыдущие ответы объясняют, почему реализация интерфейса явно в С# может быть предпочтительнее (в основном по формальным причинам). Однако есть одна ситуация, где явная реализация обязательна: во избежание утечки инкапсуляции, когда интерфейс - non- 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() { } }

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

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