C# Language
Интерфейсы
Поиск…
Внедрение интерфейса
Интерфейс используется для обеспечения наличия метода в любом классе, который «реализует» его. Интерфейс определяется interface
ключевого слова, и класс может «реализовать» его, добавив : InterfaceName
после имени класса. Класс может реализовывать несколько интерфейсов, разделяя каждый интерфейс запятой.
: InterfaceName, ISecondInterface
public interface INoiseMaker
{
string MakeNoise();
}
public class Cat : INoiseMaker
{
public string MakeNoise()
{
return "Nyan";
}
}
public class Dog : INoiseMaker
{
public string MakeNoise()
{
return "Woof";
}
}
Поскольку они реализуют INoiseMaker
, как cat
и dog
должны включать метод string MakeNoise()
и не смогут скомпилироваться без него.
Реализация нескольких интерфейсов
public interface IAnimal
{
string Name { get; set; }
}
public interface INoiseMaker
{
string MakeNoise();
}
public class Cat : IAnimal, INoiseMaker
{
public Cat()
{
Name = "Cat";
}
public string Name { get; set; }
public string MakeNoise()
{
return "Nyan";
}
}
Явная реализация интерфейса
Явная реализация интерфейса необходима, когда вы реализуете несколько интерфейсов, которые определяют общий метод, но требуются разные реализации в зависимости от того, какой интерфейс используется для вызова метода (обратите внимание, что вам не нужны явные реализации, если несколько интерфейсов используют один и тот же метод и возможна общая реализация).
interface IChauffeur
{
string Drive();
}
interface IGolfPlayer
{
string Drive();
}
class GolfingChauffeur : IChauffeur, IGolfPlayer
{
public string Drive()
{
return "Vroom!";
}
string IGolfPlayer.Drive()
{
return "Took a swing...";
}
}
GolfingChauffeur obj = new GolfingChauffeur();
IChauffeur chauffeur = obj;
IGolfPlayer golfer = obj;
Console.WriteLine(obj.Drive()); // Vroom!
Console.WriteLine(chauffeur.Drive()); // Vroom!
Console.WriteLine(golfer.Drive()); // Took a swing...
Реализация не может быть вызвана нигде, кроме как с помощью интерфейса:
public class Golfer : IGolfPlayer
{
string IGolfPlayer.Drive()
{
return "Swinging hard...";
}
public void Swing()
{
Drive(); // Compiler error: No such method
}
}
В связи с этим может оказаться целесообразным поместить сложный код реализации явно реализованного интерфейса в отдельный частный метод.
Разумеется, явная реализация интерфейса может быть использована только для тех методов, которые существуют для этого интерфейса:
public class ProGolfer : IGolfPlayer
{
string IGolfPlayer.Swear() // Error
{
return "The ball is in the pit";
}
}
Аналогичным образом, использование явной реализации интерфейса без объявления этого интерфейса в классе также вызывает ошибку.
Подсказка:
Внедрение интерфейсов также может быть использовано для предотвращения мертвого кода. Когда метод больше не нужен и удаляется из интерфейса, компилятор будет жаловаться на каждую существующую реализацию.
Замечания:
Программисты ожидают, что контракт будет одинаковым независимо от контекста типа и явной реализации не должен выставлять другое поведение при вызове. Поэтому, в отличие от приведенного выше примера, IGolfPlayer.Drive
и Drive
должны делать то же самое, когда это возможно.
Почему мы используем интерфейсы
Интерфейс - это определение контракта между пользователем интерфейса и классом, который его реализует. Один из способов думать о интерфейсе - это объявление того, что объект может выполнять определенные функции.
Предположим, что мы определяем интерфейс IShape
для представления различных типов фигур, мы ожидаем, что у формы будет область, поэтому мы определим метод, чтобы заставить реализации интерфейса вернуть свою область:
public interface IShape
{
double ComputeArea();
}
Давайте будем иметь следующие две формы: Rectangle
и Circle
public class Rectangle : IShape
{
private double length;
private double width;
public Rectangle(double length, double width)
{
this.length = length;
this.width = width;
}
public double ComputeArea()
{
return length * width;
}
}
public class Circle : IShape
{
private double radius;
public Circle(double radius)
{
this.radius = radius;
}
public double ComputeArea()
{
return Math.Pow(radius, 2.0) * Math.PI;
}
}
У каждого из них есть собственное определение его области, но оба они являются формами. Поэтому логично рассматривать их как IShape
в нашей программе:
private static void Main(string[] args)
{
var shapes = new List<IShape>() { new Rectangle(5, 10), new Circle(5) };
ComputeArea(shapes);
Console.ReadKey();
}
private static void ComputeArea(IEnumerable<IShape> shapes)
{
foreach (shape in shapes)
{
Console.WriteLine("Area: {0:N}, shape.ComputeArea());
}
}
// Output:
// Area : 50.00
// Area : 78.54
Основы интерфейса
Функция интерфейса, известная как «контракт» функциональности. Это означает, что он объявляет свойства и методы, но не реализует их.
Так что в отличие от классов Интерфейсы:
- Невозможно создать экземпляр
- Не может быть никакой функциональности
- Может содержать только методы * (Свойства и события являются методами внутри)
- Наследование интерфейса называется «Реализация»,
- Вы можете наследовать от 1 класса, но вы можете «реализовать» несколько интерфейсов
public interface ICanDoThis{
void TheThingICanDo();
int SomeValueProperty { get; set; }
}
Что следует заметить:
- Префикс «I» - это соглашение об именах, используемое для интерфейсов.
- Тело функции заменяется точкой с запятой «;».
- Свойства также разрешены, потому что внутренне они также являются методами
public class MyClass : ICanDoThis {
public void TheThingICanDo(){
// do the thing
}
public int SomeValueProperty { get; set; }
public int SomeValueNotImplemtingAnything { get; set; }
}
,
ICanDoThis obj = new MyClass();
// ok
obj.TheThingICanDo();
// ok
obj.SomeValueProperty = 5;
// Error, this member doesn't exist in the interface
obj.SomeValueNotImplemtingAnything = 5;
// in order to access the property in the class you must "down cast" it
((MyClass)obj).SomeValueNotImplemtingAnything = 5; // ok
Это особенно полезно, когда вы работаете с инфраструктурами пользовательского интерфейса, такими как WinForms или WPF, потому что для создания пользовательского элемента управления необходимо унаследовать от базового класса, и вы теряете способность создавать абстракции по различным типам управления. Пример? Прибытие:
public class MyTextBlock : TextBlock {
public void SetText(string str){
this.Text = str;
}
}
public class MyButton : Button {
public void SetText(string str){
this.Content = str;
}
}
Предложенная проблема состоит в том, что оба содержат некоторое понятие «Текст», но имена свойств различаются. И вы не можете создать создание абстрактного базового класса, потому что у них есть обязательное наследование для двух разных классов. Интерфейс может облегчить
public interface ITextControl{
void SetText(string str);
}
public class MyTextBlock : TextBlock, ITextControl {
public void SetText(string str){
this.Text = str;
}
}
public class MyButton : Button, ITextControl {
public void SetText(string str){
this.Content = str;
}
public int Clicks { get; set; }
}
Теперь MyButton и MyTextBlock взаимозаменяемы.
var controls = new List<ITextControls>{
new MyTextBlock(),
new MyButton()
};
foreach(var ctrl in controls){
ctrl.SetText("This text will be applied to both controls despite them being different");
// Compiler Error, no such member in interface
ctrl.Clicks = 0;
// Runtime Error because 1 class is in fact not a button which makes this cast invalid
((MyButton)ctrl).Clicks = 0;
/* the solution is to check the type first.
This is usually considered bad practice since
it's a symptom of poor abstraction */
var button = ctrl as MyButton;
if(button != null)
button.Clicks = 0; // no errors
}
«Скрытие» членов с явной реализацией
Разве вы не ненавидите его, когда интерфейсы загрязняют вас классом со слишком многими членами, которых вам даже не волнует? Ну, я получил решение! Явные реализации
public interface IMessageService {
void OnMessageRecieve();
void SendMessage();
string Result { get; set; }
int Encoding { get; set; }
// yadda yadda
}
Обычно вы реализуете класс следующим образом.
public class MyObjectWithMessages : IMessageService {
public void OnMessageRecieve(){
}
public void SendMessage(){
}
public string Result { get; set; }
public int Encoding { get; set; }
}
Каждый участник является публичным.
var obj = new MyObjectWithMessages();
// why would i want to call this function?
obj.OnMessageRecieve();
Ответ: Не знаю. Таким образом, ни один из них не должен быть объявлен открытым, но просто объявляя, что члены как частные, заставят компилятор выбросить ошибку
Решение заключается в использовании явной реализации:
public class MyObjectWithMessages : IMessageService{
void IMessageService.OnMessageRecieve() {
}
void IMessageService.SendMessage() {
}
string IMessageService.Result { get; set; }
int IMessageService.Encoding { get; set; }
}
Итак, теперь вы внедрили участников по мере необходимости, и они не будут публиковать публичных участников.
var obj = new MyObjectWithMessages();
/* error member does not exist on type MyObjectWithMessages.
* We've succesfully made it "private" */
obj.OnMessageRecieve();
Если вы серьезно хотите получить доступ к члену, хотя явным образом реализую все, что вам нужно сделать, это передать объект в интерфейс, и вам хорошо идти.
((IMessageService)obj).OnMessageRecieve();
IComparable как пример реализации интерфейса
Интерфейсы могут казаться абстрактными, пока вы не увидите их на практике. IComparable
и IComparable<T>
- отличные примеры того, почему интерфейсы могут быть полезны для нас.
Предположим, что в программе для интернет-магазина у нас есть множество предметов, которые вы можете купить. Каждый элемент имеет имя, идентификационный номер и цену.
public class Item {
public string name; // though public variables are generally bad practice,
public int idNumber; // to keep this example simple we will use them instead
public decimal price; // of a property.
// body omitted for brevity
}
У нас есть наш Item
хранящийся внутри List<Item>
, и в нашей программе где-то мы хотим отсортировать наш список по ID-номеру от наименьшего до самого большого. Вместо написания нашего собственного алгоритма сортировки мы можем вместо этого использовать метод Sort()
который уже имеет List<T>
. Однако, поскольку наш класс Item
находится прямо сейчас, для List<T>
нет способа понять, какой порядок сортировать список. Здесь находится интерфейс IComparable
.
Чтобы правильно реализовать метод CompareTo
, CompareTo
должен возвращать положительное число, если параметр «меньше» текущего, ноль, если они равны, и отрицательное число, если параметр «больше».
Item apple = new Item();
apple.idNumber = 15;
Item banana = new Item();
banana.idNumber = 4;
Item cow = new Item();
cow.idNumber = 15;
Item diamond = new Item();
diamond.idNumber = 18;
Console.WriteLine(apple.CompareTo(banana)); // 11
Console.WriteLine(apple.CompareTo(cow)); // 0
Console.WriteLine(apple.CompareTo(diamond)); // -3
Вот на примере Item
реализации «сек интерфейса:
public class Item : IComparable<Item> {
private string name;
private int idNumber;
private decimal price;
public int CompareTo(Item otherItem) {
return (this.idNumber - otherItem.idNumber);
}
// rest of code omitted for brevity
}
На поверхностном уровне метод CompareTo
в нашем элементе просто возвращает разницу в их идентификационных номерах, но что делает это на практике?
Теперь, когда мы вызываем Sort()
в объекте List<Item>
, List
будет автоматически вызывать метод CompareTo
Item
когда ему нужно определить, в какой порядок помещать объекты. Кроме того, помимо List<T>
, любые другие объекты которые нуждаются в способности сравнивать два объекта, будут работать с Item
потому что мы определили способность двух разных Item
s сравнивать друг с другом.