Каковы различия в реализации интерфейсов неявно и явно в С#?
Когда вы должны использовать неявное и когда следует использовать явное?
Есть ли какие-либо плюсы и/или минусы одного или другого?
Официальные рекомендации Microsoft (из первого выпуска Framework Design Guidelines) говорится, что использование явных реализаций не рекомендуется, поскольку это дает непредсказуемое поведение кода.
Я думаю, что это руководство очень действует в течение до IoC-времени, когда вы не проходите через интерфейсы.
Может ли кто-нибудь затронуть и этот аспект?
Неявный - это когда вы определяете свой интерфейс через член вашего класса. Явно - это когда вы определяете методы в своем классе на интерфейсе. Я знаю, что это звучит запутанно, но вот что я имею в виду: 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
... в противном случае вы получите ошибка
Неявное определение заключается в том, чтобы просто добавлять методы/свойства и т.д., требуемые интерфейсом непосредственно к классу, как общедоступные методы.
Явное определение заставляет участников подвергаться воздействию только при непосредственном взаимодействии с интерфейсом, а не в основной реализации. Это предпочтительнее в большинстве случаев.
В дополнение к отличным ответам, уже предоставленным, есть некоторые случаи, когда явная реализация НЕОБХОДИМА для того, чтобы компилятор смог выяснить, что требуется. Взгляните на 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.
abstract
.
Процитировать Джеффри Рихтера от CLR через С#
( EIMI означает E xplicit I nterface M этад I реализация)
Критически важно для вас понять некоторые последствия, которые существуют при использовании EIMI. И из-за эти последствия, вы должны попытаться избегайте EIMI как можно больше. К счастью, общие интерфейсы помогают вы избегаете EIMI совсем немного. Но есть все еще могут быть временами, когда вам понадобятся использовать их (например, реализовать два методы интерфейса с тем же именем и подпись). Вот большой проблемы с EIMI:
- Нет документации, объясняющей, как конкретно тип реализует метод EIMI, и там не является Microsoft Visual Studio Поддержка IntelliSense.
- Типы типов экземпляров помещаются в поле при переносе в интерфейс.
- EIMI не может быть вызван производным типом.
Если вы используете ссылку на интерфейс, любая виртуальная цепочка может быть явно заменена EIMI на любом производном классе, и когда объект такого типа передается в интерфейс, ваша виртуальная цепочка игнорируется и вызывается явная реализация. Это ничего, кроме полиморфизма.
EIMI также могут использоваться для скрытия нежестко типизированных членов интерфейса из реализаций базовых интерфейсов Framework, таких как IEnumerable <T> , поэтому ваш класс не подвергает непосредственно не строго типизированный метод, а является синтаксическим правилом.
Я хочу использовать явную реализацию интерфейса, когда хочу препятствовать "программированию реализации" (Принципы проектирования из шаблонов проектирования).
Например, в веб-приложении presenter) с меньшей вероятностью будет зависеть от реализации StandardNavigator и, скорее всего, будет зависеть от INavigator интерфейса (поскольку реализация должна быть применена к интерфейсу для использования метода Redirect).
Еще одна причина, по которой я мог бы пойти с явной реализацией интерфейса, заключалась бы в том, чтобы очистить интерфейс класса по умолчанию. Например, если я разрабатывал серверный элемент 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; }
}
Ведущий заполняет источник данных, и разработчик веб-страницы никогда не должен знать о его существовании.
Я бы не рекомендовал всегда использовать явные реализации интерфейса. Это всего лишь два примера, где они могут быть полезны.
В дополнение к другим причинам, уже заявленным, это та ситуация, в которой класс реализует два разных интерфейса, которые имеют свойство/метод с тем же именем и сигнатурой.
/// <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 в первую очередь, иначе код не будет компилироваться.
Я использую явную реализацию интерфейса большую часть времени. Вот основные причины.
Рефакторинг более безопасен
При изменении интерфейса лучше, если компилятор может его проверить. Это сложнее с неявными реализациями.
Приходят в голову два распространенных случая:
Добавление функции в интерфейс, где уже существует существующий класс, реализующий этот интерфейс, имеет метод с той же сигнатурой, что и новый. Это может привести к неожиданному поведению и несколько раз укусил меня. Сложно "видеть" при отладке, потому что эта функция, вероятно, не находится с другими методами интерфейса в файле (проблема самодокументирования, упомянутая ниже).
Удаление функции из интерфейса. Неявно реализованные методы будут внезапно мертвым кодом, но явно реализованные методы поймаются при ошибке компиляции. Даже если мертвый код хорош, чтобы оставаться в стороне, я хочу быть вынужденным пересмотреть его и продвинуть.
Несчастливо, что у С# нет ключевого слова, которое заставляет нас отмечать метод как неявную реализацию, поэтому компилятор может выполнить дополнительные проверки. Виртуальные методы не имеют ни одной из вышеуказанных проблем из-за необходимости использования "переопределения" и "нового".
Примечание. Для фиксированных или редко меняющихся интерфейсов (как правило, из API поставщиков) это не проблема. Однако для моих собственных интерфейсов я не могу предсказать, когда и как они будут меняться.
Это самодокументируемое
Если я вижу "public bool Execute()" в классе, потребуется дополнительная работа, чтобы понять, что это часть интерфейса. Кто-то, вероятно, должен будет прокомментировать это, сказав это, или поместив его в группу других реализаций интерфейса, все под регионом или группировкой комментариев, говорящих "реализация ITask". Конечно, это работает только в том случае, если заголовок группы не выключен.
В то время как: bool ITask.Execute() 'ясен и недвусмыслен.
Четкое разделение реализации интерфейса
Я думаю, что интерфейсы являются более "публичными", чем общедоступными, потому что они созданы, чтобы выставить только немного поверхности конкретного типа. Они уменьшают тип до способности, поведения, набора признаков и т.д. И в реализации я считаю полезным сохранять это разделение.
Как я просматриваю код класса, когда я сталкиваюсь с явными реализациями интерфейса, мой мозг переходит в режим "кодового контракта". Часто эти реализации просто переходят к другим методам, но иногда они выполняют дополнительную проверку состояния/параметра, преобразование входящих параметров для лучшего соответствия внутренним требованиям или даже перевод для целей управления версиями (т.е. Несколько поколений интерфейсов, которые полностью соответствуют общим реализациям).
(Я понимаю, что общественность также является кодовым контрактом, но интерфейсы намного сильнее, особенно в управляемой интерфейсом кодовой базе, где прямое использование конкретных типов обычно является признаком внутреннего кода.)
Связанный: Причина 2 выше Jon.
И так далее
Плюс преимущества, уже упомянутые в других ответах здесь:
Это не все весело и весело. Есть некоторые случаи, когда я придерживаюсь implicits:
Кроме того, может быть больно делать кастинг, когда у вас действительно есть конкретный тип и вы хотите вызвать явный метод интерфейса. Я имею дело с этим одним из двух способов:
public IMyInterface I { get { return this; } }
(который должен быть вставлен) и вызовите foo.I.InterfaceMethod()
. Если несколько интерфейсов, которые нуждаются в этой способности, расширьте имя за пределами я (по моему опыту, мне редко приходится это делать).Если вы реализуете явно, вы сможете ссылаться только на элементы интерфейса через ссылку, относящуюся к типу интерфейса. Ссылка, являющаяся типом реализующего класса, не будет выставлять те элементы интерфейса.
Если ваш класс реализации не является общедоступным, за исключением метода, используемого для создания класса (который может быть контейнером factory или IoC), и за исключением методов интерфейса (конечно), то я не вижу никакого преимущества для явного внедрения интерфейсов.
В противном случае явное внедрение интерфейсов гарантирует, что ссылки на ваш конкретный класс реализации не будут использоваться, что позволит вам впоследствии изменить эту реализацию. "Совершенно верно", я полагаю, является "преимуществом". Хорошо реализованная реализация может выполнить это без явной реализации.
Недостатком, на мой взгляд, является то, что вы обнаружите, что вы используете типы литья в/из интерфейса в коде реализации, который имеет доступ к непубличным членам.
Как и многие вещи, преимущество - недостаток (и наоборот). Явное внедрение интерфейсов гарантирует, что ваш конкретный код реализации класса не будет показан.
Неявная реализация интерфейса - это то, где у вас есть метод с той же сигнатурой интерфейса.
Явная реализация интерфейса - это то, где вы явно объявляете, к какому интерфейсу относится этот метод.
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()");
}
}
Каждый член класса, который реализует интерфейс, экспортирует объявление, которое семантически похоже на то, как написаны декларации интерфейса 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 ... }
Не так приятно.
Одним из важных применений явной реализации интерфейса является необходимость использования интерфейсов с смешанной видимостью.
Проблема и решение хорошо объяснены в статье Внутренний интерфейс С#.
Например, если вы хотите защитить утечку объектов между уровнями приложений, этот метод позволяет указать различную видимость элементов, которые могут вызвать утечку.