Итак, какой именно вариант использования для явной реализации интерфейса?
Это только для того, чтобы людям, использующим класс, не нужно было смотреть на все эти методы / свойства в intellisense?
Итак, какой именно вариант использования для явной реализации интерфейса?
Это только для того, чтобы людям, использующим класс, не нужно было смотреть на все эти методы / свойства в intellisense?
Ответы:
Если вы реализуете два интерфейса, как с одним и тем же методом, так и с разными реализациями, вы должны реализовать явно.
public interface IDoItFast
{
void Go();
}
public interface IDoItSlow
{
void Go();
}
public class JustDoIt : IDoItFast, IDoItSlow
{
void IDoItFast.Go()
{
}
void IDoItSlow.Go()
{
}
}
Полезно скрыть нежелательный член. Например, если вы реализуете и то, IComparable<T>и другое, и IComparableобычно лучше скрыть IComparableперегрузку, чтобы у людей не сложилось впечатление, что вы можете сравнивать объекты разных типов. Точно так же некоторые интерфейсы не совместимы с CLS, например IConvertible, поэтому, если вы явно не реализуете интерфейс, конечные пользователи языков, требующих соответствия CLS, не смогут использовать ваш объект. (Что было бы очень катастрофически, если бы разработчики BCL не скрывали IConvertible членов примитивов :))
Еще одно интересное замечание: обычно использование такой конструкции означает, что структура, которая явно реализует интерфейс, может вызывать их только путем упаковки в тип интерфейса. Вы можете обойти это, используя общие ограничения:
void SomeMethod<T>(T obj) where T:IConvertible
Не будет упаковывать int, когда вы передадите ему.
stringсуществовала до дженериков, и эта практика была в моде. Когда появился .net 2, они не хотели нарушать публичный интерфейс, stringпоэтому оставили его в том же виде, как и с установленной защитой.
Некоторые дополнительные причины для явной реализации интерфейса:
обратная совместимость : в случае 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методом клонирования и запечатанные классы, которые являются производными от них и предоставляют публичное клонирование.
Еще один полезный метод - заставить публичную реализацию метода функции возвращать значение, более конкретное, чем указано в интерфейсе.
Например, объект может быть реализован ICloneable, но его общедоступный Cloneметод по- прежнему может возвращать собственный тип.
Точно так же у an IAutomobileFactoryможет быть Manufactureметод, который возвращает Automobile, но a FordExplorerFactory, который реализует IAutomobileFactory, может иметь свой Manufactureметод, возвращающий a FordExplorer(который является производным от Automobile). Код, который знает, что у него есть, FordExplorerFactoryможет использовать FordExplorer-специфичные свойства для объекта, возвращаемого a, FordExplorerFactoryбез необходимости приводить тип, в то время как код, который просто знал, что у него есть какой-то тип IAutomobileFactory, просто обрабатывал бы его возвращение как Automobile.
Это также полезно, когда у вас есть два интерфейса с одним и тем же именем члена и подписью, но вы хотите изменить его поведение в зависимости от того, как он используется. (Я не рекомендую писать такой код):
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
}
public class Animal : Cat, Dog
Он может сохранять общедоступный интерфейс более чистым для явной реализации интерфейса, т.е. ваш Fileкласс может реализовывать IDisposableявно и предоставлять общедоступный метод, Close()который может иметь больше смысла для потребителя, чем Dispose().
F # предлагает только явную реализацию интерфейса, поэтому вам всегда нужно выполнять приведение к конкретному интерфейсу, чтобы получить доступ к его функциональности, что делает использование интерфейса очень явным (без каламбура).
Dispose- это те, которые никогда не потребуют очистки); лучшим примером может быть что-то вроде реализации неизменяемой коллекции IList<T>.Add.
Другая причина явной реализации - ремонтопригодность .
Когда класс становится «занятым» - да, такое случается, мы не можем позволить себе роскошь рефакторинга кода других членов команды - тогда наличие явной реализации дает понять, что существует метод, удовлетворяющий контракту интерфейса.
Таким образом, улучшается «читаемость» кода.
#regionсоответствующая строка заголовка. И комментарий к методу.
Другой пример приводится 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();
В случае явно определенных интерфейсов все методы автоматически становятся частными, вы не можете предоставить им общедоступный модификатор доступа. Предположим, что:
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
Вот как мы можем создать явный интерфейс: если у нас есть 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();
}
}
}