Отже, що саме є корисним випадком використання для явного впровадження інтерфейсу?
Це лише так, що людям, які використовують клас, не потрібно дивитися на всі ці методи / властивості в інтелісенсі?
Отже, що саме є корисним випадком використання для явного впровадження інтерфейсу?
Це лише так, що людям, які використовують клас, не потрібно дивитися на всі ці методи / властивості в інтелісенсі?
Відповіді:
Якщо ви реалізуєте два інтерфейси, як з тим самим методом, так і з різними реалізаціями, тоді вам доведеться явно реалізувати.
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
Не буде поле, коли ви перейдете до нього.
string
раніше навколо дженериків, і ця практика була в моді. Коли .net 2 прийшов навколо, вони не хотіли порушувати публічний інтерфейс, string
тому вони залишили його навколо, як це було з захисною системою на місці.
Деякі додаткові причини, щоб явно реалізувати інтерфейс:
зворотна сумісність : у разі ICloneable
зміни інтерфейсу, учасникам класу методу реалізації не потрібно змінювати свої підписи методів.
чистіший код : у випадку Clone
видалення методу з ICloneable буде помилка компілятора , проте якщо ви реалізуєте метод неявно, ви можете отримати невикористані загальнодоступні загальнодоступні методи
сильне введення тексту : Щоб проілюструвати історію supercat на прикладі, це був би мій кращий зразок коду, реалізація 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. Хоча об'єкт, як правило, може бути безпечно клонований лише тоді, коли його база може бути, можна побажати виходити з базового класу, який міг би бути безпечно клонований об'єктом класу, який не може. Щоб дозволити це, я б рекомендував не мати спадкових класів піддавати відкритий метод клонування. Натомість мають успадковані класи protected
методом клонування та запечатані класи, які походять від них та піддають публічному клонуванню.
Інший корисний прийом - це публічна реалізація функції методу, що повертає значення, яке є більш конкретним, ніж зазначено в інтерфейсі.
Наприклад, об’єкт може реалізовувати ICloneable
, але все-таки мати свій загальнодоступний Clone
метод повернення власного типу.
Так само IAutomobileFactory
може бути Manufacture
метод, який повертає Automobile
, але FordExplorerFactory
, який реалізує IAutomobileFactory
, може мати його Manufacture
метод return a FordExplorer
(який походить від Automobile
). Код, який знає, що він FordExplorerFactory
може використовуватиFordExplorer
специфічні властивості для об'єкта, поверненого 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();
}
}
}