Зачем реализовывать интерфейс явно?


122

Итак, какой именно вариант использования для явной реализации интерфейса?

Это только для того, чтобы людям, использующим класс, не нужно было смотреть на все эти методы / свойства в intellisense?

Ответы:


146

Если вы реализуете два интерфейса, как с одним и тем же методом, так и с разными реализациями, вы должны реализовать явно.

public interface IDoItFast
{
    void Go();
}
public interface IDoItSlow
{
    void Go();
}
public class JustDoIt : IDoItFast, IDoItSlow
{
    void IDoItFast.Go()
    {
    }

    void IDoItSlow.Go()
    {
    }
}

Да, именно этот случай решает EIMI. И другие моменты охвачены ответом «Майкл Б.».
зашифровано

10
Отличный пример. Обожаю имена интерфейсов / классов! :-)
Брайан Роджерс

11
Мне это не нравится, два метода с одинаковой сигнатурой в классе, которые делают очень разные вещи? Это чрезвычайно опасная штука, которая, скорее всего, вызовет хаос в любой крупной разработке. Если у вас есть такой код, я бы сказал, что ваш анализ и дизайн на высоте.
Мик

4
@Mike Интерфейсы могут принадлежать какому-либо API или двум разным API. Может быть, любовь здесь немного преувеличена, но я, по крайней мере, был бы рад, что доступна явная реализация.
TobiMcNamobi

@BrianRogers И имена методов тоже ;-)
Sнаđошƒаӽ 07

66

Полезно скрыть нежелательный член. Например, если вы реализуете и то, IComparable<T>и другое, и IComparableобычно лучше скрыть IComparableперегрузку, чтобы у людей не сложилось впечатление, что вы можете сравнивать объекты разных типов. Точно так же некоторые интерфейсы не совместимы с CLS, например IConvertible, поэтому, если вы явно не реализуете интерфейс, конечные пользователи языков, требующих соответствия CLS, не смогут использовать ваш объект. (Что было бы очень катастрофически, если бы разработчики BCL не скрывали IConvertible членов примитивов :))

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

void SomeMethod<T>(T obj) where T:IConvertible

Не будет упаковывать int, когда вы передадите ему.


1
В вашем ограничении допущена опечатка. Чтобы прояснить, приведенный выше код действительно работает. Это должно быть в исходном объявлении сигнатуры метода в интерфейсе. В исходном посте это не уточнялось. Кроме того, правильный формат - "void SomeMehtod <T> (T obj), где T: IConvertible. Обратите внимание, что между") "и" where "есть дополнительное двоеточие, которого не должно быть. Тем не менее, +1 для умного использования дженерики, чтобы избежать дорогого бокса.
Зак Яннсен

1
Привет, Майкл Б. Итак, почему при реализации строки в .NET существует публичная реализация IComparable: public int CompareTo (Object value) {if (value == null) {return 1; } if (! (значение String)) {выбросить новое исключение ArgumentException (Environment.GetResourceString ("Arg_MustBeString")); } return String.Compare (this, (String) value, StringComparison.CurrentCulture); } Спасибо!
zzfima

stringсуществовала до дженериков, и эта практика была в моде. Когда появился .net 2, они не хотели нарушать публичный интерфейс, stringпоэтому оставили его в том же виде, как и с установленной защитой.
Майкл Б.

37

Некоторые дополнительные причины для явной реализации интерфейса:

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

более чистый код : будет ошибка компилятора, если Cloneметод будет удален из ICloneable, однако, если вы реализуете метод неявно, вы можете получить неиспользуемые «осиротевшие» общедоступные методы

строгая типизация : чтобы проиллюстрировать историю суперкота на примере, это был бы мой предпочтительный пример кода, реализация которого ICloneableявно позволяет Clone()строго типизировать, когда вы вызываете его напрямую как MyObjectчлен экземпляра:

public class MyObject : ICloneable
{
  public MyObject Clone()
  {
    // my cloning logic;  
  }

  object ICloneable.Clone()
  {
    return this.Clone();
  }
}

Для этого я бы предпочел interface ICloneable<out T> { T Clone(); T self {get;} }. Обратите внимание, что ICloneable<T>на T. намеренно нет ограничений на T. Хотя объект обычно можно безопасно клонировать только в том случае, если это возможно, можно унаследовать от базового класса, который может быть безопасно клонирован, объект класса, который не может. Чтобы учесть это, я бы не рекомендовал, чтобы наследуемые классы предоставляли открытый метод клонирования. Вместо этого имейте наследуемые классы с protectedметодом клонирования и запечатанные классы, которые являются производными от них и предоставляют публичное клонирование.
supercat

Конечно, это было бы лучше, за исключением того, что в BCL нет ковариантной версии ICloneable, так что вам придется ее создать, верно?
Wiebe Tijsma

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

13

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

Например, объект может быть реализован ICloneable, но его общедоступный Cloneметод по- прежнему может возвращать собственный тип.

Точно так же у an IAutomobileFactoryможет быть Manufactureметод, который возвращает Automobile, но a FordExplorerFactory, который реализует IAutomobileFactory, может иметь свой Manufactureметод, возвращающий a FordExplorer(который является производным от Automobile). Код, который знает, что у него есть, FordExplorerFactoryможет использовать FordExplorer-специфичные свойства для объекта, возвращаемого a, FordExplorerFactoryбез необходимости приводить тип, в то время как код, который просто знал, что у него есть какой-то тип IAutomobileFactory, просто обрабатывал бы его возвращение как Automobile.


2
+1 ... это было бы моим предпочтительным использованием явных реализаций интерфейса, хотя небольшой образец кода, вероятно, был бы немного более понятным, чем эта история :)
Wiebe Tijsma

7

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

interface Cat
{
    string Name {get;}
}

interface Dog
{
    string Name{get;}
}

public class Animal : Cat, Dog
{
    string Cat.Name
    {
        get
        {
            return "Cat";
        }
    }

    string Dog.Name
    {
        get
        {
            return "Dog";
        }
    }
}
static void Main(string[] args)
{
    Animal animal = new Animal();
    Cat cat = animal; //Note the use of the same instance of Animal. All we are doing is picking which interface implementation we want to use.
    Dog dog = animal;
    Console.WriteLine(cat.Name); //Prints Cat
    Console.WriteLine(dog.Name); //Prints Dog
}

60
Самый странный пример, связанный с объектно-ориентированной public class Animal : Cat, Dog
архитектурой, который

38
@mbx: Если бы Animal также реализовал Parrot, это было бы животное, изменяющее Polly.
RenniePet

3
Я помню мультипликационный персонаж, котом на одном конце и собакой на другом ;-)
Джордж Бирбилис

2
в 80-х был телесериал ... "Manimal" ... где человек мог превратиться в ... о, да
ладно

Морки вроде бы похожи на кошек, и тоже на кошек-ниндзя.
samis

6

Он может сохранять общедоступный интерфейс более чистым для явной реализации интерфейса, т.е. ваш Fileкласс может реализовывать IDisposableявно и предоставлять общедоступный метод, Close()который может иметь больше смысла для потребителя, чем Dispose().

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


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

1
@Gabe - это более тонко, чем для VB - именование и доступность членов, реализующих интерфейс, отделены от указания того, что они являются частью реализации. Итак, в VB и глядя на ответ @Iain (текущий верхний ответ), вы можете реализовать IDoItFast и IDoItSlow с общедоступными членами «GoFast» и «GoSlow» соответственно.
Damien_The_Unbeliever

2
Мне не нравится ваш конкретный пример (IMHO, единственное, что следует скрывать, Dispose- это те, которые никогда не потребуют очистки); лучшим примером может быть что-то вроде реализации неизменяемой коллекции IList<T>.Add.
supercat

5

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


Хорошо, это объясняет, почему проект не компилируется с неявной реализацией.
pauloya

4

Другая причина явной реализации - ремонтопригодность .

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

Таким образом, улучшается «читаемость» кода.


ИМХО, более важно решить, должен ли класс предоставлять метод своим клиентам или нет. Это побуждает быть явным или неявным. Чтобы задокументировать, что несколько методов принадлежат друг другу, в данном случае потому, что они удовлетворяют контракту - вот для чего нужна #regionсоответствующая строка заголовка. И комментарий к методу.
ToolmakerSteve

1

Другой пример приводится System.Collections.Immutableв том, что авторы решили использовать эту технику для сохранения знакомого API для типов коллекций, удаляя при этом части интерфейса, которые не имеют смысла для их новых типов.

В частности, ImmutableList<T>орудия IList<T>и , таким образом ICollection<T>( в порядке , чтобы ImmutableList<T>использоваться более легко с унаследованным кодом), но void ICollection<T>.Add(T item)не имеет смысла для ImmutableList<T>: так как добавление элемента к неизменному списка не должны изменить существующий список, ImmutableList<T>также происходит из IImmutableList<T>которых IImmutableList<T> Add(T item)может быть использован для неизменяемые списки.

Таким образом, в случае Addреализации в ImmutableList<T>конечном итоге будут выглядеть следующим образом:

public ImmutableList<T> Add(T item)
{
    // Create a new list with the added item
}

IImmutableList<T> IImmutableList<T>.Add(T value) => this.Add(value);

void ICollection<T>.Add(T item) => throw new NotSupportedException();

int IList.Add(object value) => throw new NotSupportedException();

0

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

interface Iphone{

   void Money();

}

interface Ipen{

   void Price();
}


class Demo : Iphone, Ipen{

  void Iphone.Money(){    //it is private you can't give public               

      Console.WriteLine("You have no money");
  }

  void Ipen.Price(){    //it is private you can't give public

      Console.WriteLine("You have to paid 3$");
  }

}


// So you have to cast to call the method


    class Program
    {
        static void Main(string[] args)
        {
            Demo d = new Demo();

            Iphone i1 = (Iphone)d;

            i1.Money();

            ((Ipen)i1).Price();

            Console.ReadKey();
        }
    }

  // You can't call methods by direct class object

1
«все методы автоматически становятся приватными» - это технически неверно, b / c, если бы они были на самом деле приватными, то их вообще нельзя было бы вызывать, будь то кастинг или нет.
samis

0

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

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace oops3
{
    interface I5
    {
        void getdata();    
    }
    interface I6
    {
        void getdata();    
    }

    class MyClass:I5,I6
    {
        void I5.getdata()
        {
           Console.WriteLine("I5 getdata called");
        }
        void I6.getdata()
        {
            Console.WriteLine("I6 getdata called");
        }
        static void Main(string[] args)
        {
            MyClass obj = new MyClass();
            ((I5)obj).getdata();                     

            Console.ReadLine();    
        }
    }
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.