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

В чем разница в реализации интерфейсов неявно и явно в C#?

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

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


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

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

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

Полная статья об интерфейсах C#: planetofcoders.com/c-interfaces

Gaurav Agrawal 17.05.2012 13:50

Да, явных интерфейсов следует избегать, и более профессиональный подход к реализации ISP (принцип разделения интерфейсов) - вот подробная статья о том же codeproject.com/Articles/1000374/….

Shivprasad Koirala 15.06.2015 06:31
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
649
2
154 664
12
Перейти к ответу Данный вопрос помечен как решенный

Ответы 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.

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

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

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

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

jharr100 18.11.2014 22:30

CLR Джеффри Рихтера через C# 4 ed ch 13 показывает пример, в котором приведение типов не требуется: внутренняя структура SomeValueType: IComparable {private Int32 m_x; общедоступный SomeValueType (Int32 x) {m_x = x; } public Int32 CompareTo (SomeValueType другой) {...);} Int32 IComparable.CompareTo (Объект другой) {return CompareTo ((SomeValueType) другой); }} public static void Main () {SomeValueType v = new SomeValueType (0); Объект o = новый объект (); Int32 n = v.CompareTo (v); // Без бокса n = v.CompareTo (o); // ошибка времени компиляции}

Andy Dent 26.08.2015 08:18

В любом случае имеет смысл иметь обе реализации, неявную и явную?

Raskolnikov 17.12.2015 15:20

Сегодня я столкнулся с редкой ситуацией, которая ТРЕБУЕТ использования явного интерфейса: класс с полем, сгенерированным построителем интерфейса, который создает это поле как частное (Xamarin нацелен на iOS, с использованием раскадровки iOS). И интерфейс, в котором имеет смысл раскрыть это поле (общедоступное только для чтения). Я мог бы изменить имя получателя в интерфейсе, но существующее имя было наиболее логичным именем для объекта. Поэтому вместо этого я сделал явную реализацию, относящуюся к частному полю: UISwitch IScoreRegPlayerViewCell.markerSwitch { get { return markerSwitch; } }.

ToolmakerSteve 13.07.2016 02:11

Ужасы программирования. Что ж, красиво развенчано!

Liquid Core 03.05.2018 14:54

@ToolmakerSteve другая ситуация (довольно распространенная), которая требует явной реализации хотя бы одного члена интерфейса, реализует несколько интерфейсов, которые имеют члены с одинаковой сигнатурой, но с разными типами возвращаемых значений. Это может произойти из-за наследования интерфейсов, как в случае с IEnumerator<T>.Current, IEnumerable<T>.GetEnumerator() и ISet<T>.Add(T). Это упоминается в другой ответ.

phoog 21.05.2020 01:13

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

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

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

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

mattlant 27.09.2008 15:15

Я не уверен, что согласен с пунктом C. Объект Cat может реализовывать IEatable, но Eat () - это основная часть этого. Могут быть случаи, когда вы захотите просто вызвать Eat () на Cat, когда вы используете «необработанный» объект, а не через интерфейс IEatable, не так ли?

LegendLength 06.05.2009 16:50

Я знаю некоторые места, где Cat действительно IEatable без возражений.

Humberto 14.06.2010 05:37

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

Valentin Kuzub 09.02.2011 21:49

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

Zack Jannsen 18.09.2012 17:12

См. Также: stackoverflow.com/questions/4103300/…

Zack Jannsen 18.09.2012 17:13

«Несколько» лет спустя для комментариев, но в любом случае: если у вас есть явная реализация, делать что-либо, кроме указания на неявную реализацию, это проблематично. Скажем, у вас есть class MyClass : IInterface, где IInterface требует string Name { get; }, а у вас есть экземпляр MyClass my = new MyClass();. С вашим предложением my.Name вернет что-то отличное от ((IInterface)my).Name, что является нарушением принципа замены Лискова. И это не только академическое, но и может обернуться ошибкой, которую очень сложно найти.

UweB 10.01.2020 13:54

-1 «Явное определение ... Это предпочтительнее в большинстве случаев». Это вопрос мнения. Я не согласен. (Я занимаюсь объектно-ориентированным дизайном с рабочих станций Xerox Dandelion в начале 80-х годов, и с тех пор использую широкий спектр языков; в течение этих десятилетий я активно изучал объектно-ориентированный дизайн.)

ToolmakerSteve 22.05.2020 02:16

Пункт 3 очень убедителен. «недостаточная краткость ваших классов (мое мнение)» можно перефразировать как «нарушение принципа разделения интерфейсов SOLID».

Arialdo Martini 06.12.2020 11:22

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

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

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

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

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

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

Daniel Eagle 02.12.2014 21:21

Помимо уже предоставленных отличных ответов, в некоторых случаях ТРЕБУЕТСЯ явная реализация, чтобы компилятор мог определить, что требуется. Взгляните на 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.

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

Matthew Scharley 27.06.2011 02:17

@Tassadaque, вы правы, но я думаю, что смысл сообщения Мэтью Шарли выше не утерян.

funkymushroom 07.07.2016 20:32

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

/// <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.

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

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

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

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

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

Процитируем Джеффри Рихтера из CLR через C#
(EIMI означает Explicit яnterface Method яmplementation)

It is critically important for you to understand some ramifications that exist when using EIMIs. And because of these ramifications, you should try to avoid EIMIs as much as possible. Fortunately, generic interfaces help you avoid EIMIs quite a bit. But there may still be times when you will need to use them (such as implementing two interface methods with the same name and signature). Here are the big problems with EIMIs:

  • There is no documentation explaining how a type specifically implements an EIMI method, and there is no Microsoft Visual Studio IntelliSense support.
  • Value type instances are boxed when cast to an interface.
  • An EIMI cannot be called by a derived type.

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

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

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

supercat 31.05.2014 00:10

@Valentin Что означают EIMI и IEMI?

Dzienny 09.06.2014 16:50

Явная реализация метода интерфейса

scobi 15.08.2014 11:32

Тогда должен IEMI не быть IIMI?

gravidThoughts 02.11.2014 17:43

-1 за «Обычно я считаю интерфейсы полу (в лучшем случае) функцией ООП, она обеспечивает наследование, но не обеспечивает настоящего полиморфизма». Я категорически не согласен. Напротив, все интерфейсы - это полиморфизм, а не наследование. Они приписывают одному типу несколько классификаций. Избегайте IEMI, когда можете, и делегируйте, как предлагает @supercat, когда вы не можете. Не избегайте интерфейсов.

Aluan Haddad 04.02.2016 06:17

«EIMI не может быть вызван производным типом». << ЧТО? Это не правда. Если я явно реализую интерфейс для типа, то я получаю его от этого типа, я все равно могу привести его к интерфейсу для вызова метода, точно так же, как я должен был бы это сделать для типа, для которого он реализован. Так что не понимаю, о чем вы говорите. Даже внутри производного типа я могу просто привести «this» к рассматриваемому интерфейсу, чтобы получить доступ к явно реализованному методу.

Triynko 01.11.2018 00:09

EIMI - это «явная реализация интерфейса член». Не все члены интерфейса являются методами; некоторые свойства.

phoog 21.05.2020 01:17

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

И так далее

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

Проблемы

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

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

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

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

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

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