При множественной реализации интерфейсов мы можем столкнуться с таким поведением: если класс реализует несколько интерфейсов, содержащих член с одинаковой сигнатурой (например, метод), то при реализации такого члена в классе оба интерфейса будут использовать этот член в качестве собственной реализации. Чтобы избежать такого поведения, нам потребуется явная реализация интерфейса. Более того, если в нашем приложении будет большое количество различных классов, реализующих один и тот же интерфейс, то нам необходимо будет реализовать все члены этого интерфейса в каждом классе. Хоть это и не является проблемой, как таковой (это обычная работа с интерфейсами в C# 7.0 и более ранних версиях), однако, начиная с C# 8.0 мы можем создавать реализации по умолчанию непосредственно при определении интерфейса.
Явная реализация интерфейса
Вначале рассмотрим вариант явной реализации интерфейса. Вернемся к нашим интерфейсам для управления деньгами и добавим ещё один интерфейс:
interface IMoneyVault
{
int MoneyAdd(int count);
int MoneyRemove(int count);
}
interface IPocket
{
int MoneyAdd(int count);
int MoneyRemove(int count);
}
Теперь у нас есть два интерфейса, которые содержат по два метода с одной и той же сигнатурой. Попробуем реализовать эти интерфейсы в нашем классе.
class Person : IMoneyVault, IPocket
{
private int money;
private void SendMessage(string text)
{
Console.WriteLine(text);
}
public int MoneyAdd(int count)
{
money += count;
SendMessage($"Денег в кошельке: {money}");
return money;
}
public int MoneyRemove(int count)
{
money -= count;
SendMessage($"Денег в кошельке: {money}");
return money;
}
}
Теперь попробуем создать две переменные с типа интерфейсов и проверим результат:
IPocket pocket = new Person(); IMoneyVault moneyVault = new Person(); pocket.MoneyAdd(100); moneyVault.MoneyAdd(100);
Так как мы определили в классе методы MoneyAdd() и MoneyRemove() только в одном варианте, то оба интерфейса будут считать эти методы своими, то есть в консоли мы увидим следующий вывод:
Денег в кошельке: 100
Чтобы мы могли использовать методы для каждого интерфейса, мы должны обеспечить их явную реализацию.
Явная реализация интерфейса — это член класса, который вызывается только через указанный интерфейс. Перепишем код класса следующим образом:
class Person : IMoneyVault, IPocket
{
private int moneyInVault;
private int moneyInPocket;
int IMoneyVault.MoneyAdd(int count)
{
moneyInVault += count;
SendMessage($"Денег в кошельке {moneyInVault}");
return moneyInVault;
}
int IPocket.MoneyAdd(int count)
{
moneyInPocket += count;
SendMessage($"Денег в кармане {moneyInPocket}");
return moneyInPocket;
}
int IMoneyVault.MoneyRemove(int count)
{
moneyInVault -= count;
SendMessage($"Денег в кошельке {moneyInVault}");
return moneyInVault;
}
int IPocket.MoneyRemove(int count)
{
moneyInPocket -= count;
SendMessage($"Денег в кармане {moneyInPocket}");
return moneyInPocket;
}
private void SendMessage(string text)
{
Console.WriteLine(text);
}
}
Чтобы явно реализовать интерфейс мы должны придерживаться следующих правил:
- реализуемый член интерфейса не должен иметь никаких модификаторов доступа
- перед именем члена через точку указывается имя интерфейса
Теперь запустим приложение и убедимся, что используются оба метода:
Денег в кошельке 100
При этом, с явной реализацией связан следующий момент — использовать явно реализованные члены интерфейса можно только через указанный интерфейс. Например, следующий код не будет скомпилирован:
Person person = new Person(); person.MoneyAdd(100);
Хотя в классе Person() явно реализованы оба интерфейса, доступ к методу мы получить не можем:
Обращаться к явным реализациям интерфейса мы можем только через интерфейс. В этом случае нам необходимо либо действовать так, как было показано выше — объявить переменную типа интерфейса, либо использовать приведение типов, например так:
Person person = new Person(); if (person is IPocket) (person as IPocket).MoneyAdd(100);
Реализация по умолчанию
Начиная с C# 8.0 интерфейсы могут содержать реализации по умолчанию. Например, создадим интерфейс с реализацией по умолчанию его свойства:
interface IEntity
{
public string Id { get
{
return Guid.NewGuid().ToString();
}
}
string Name { get;}
}
Теперь реализуем этот интерфейс в нашем классе Person:
class Person : IMoneyVault, IPocket, IEntity
{
private int moneyInVault;
private int moneyInPocket;
public string Name => "Вася Пупкин";
//Тут реализации прочих интерфейсов и методы класса
}
Обратите внимание, что наш класс НЕ реализует свойство Id интерфейса так как оно имеет реализацию по умолчанию в самом интерфейсе. Теперь, чтобы получить доступ к свойству Id мы также должны воспользоваться приведением типов. Например,
Person person = new Person();
person.SendMessage($"Id = {(person as IEntity).Id}; Имя {person.Name}");
в консоли мы увидим вот такой вывод:
Если теперь мы реализуем в нашем классе точно такое же свойство, как и у интерфейса, то именно это свойство будет использоваться объектом. Например:
class Person : IMoneyVault, IPocket, IEntity
{
private int moneyInVault;
private int moneyInPocket;
public string Id { get => "abcd"; }
public string Name => "Вася Пупкин";
//здесь прочие реализации интерфейсов и методы класса
}
Теперь наш класс содержит свойство Id, а также реализует интерфейс с реализацией свойства Id по умолчанию. В этом случае, даже при приведении типов, значение свойства будет тем, которое определено непосредственно в классе:
Person person = new Person();
person.SendMessage($"Id = {(person as IEntity).Id}; Имя {person.Name}");
Таким образом, реализация интерфейса по умолчанию позволяет расширить функциональность интерфейса, не прибегая при этом, к реализации новых его членах во всех классах, реализующих этот интерфейс.
Итого
Явная реализация интерфейса — это член класса, который вызывается только через указанный интерфейс. Использование явной реализации позволяет использовать члены различных интерфейсов, имеющих одну и ту же сигнатуру. Начиная с C# 8.0 можно создавать реализации по умолчанию различных членов интерфейса непосредственно в самом интерфейсе. Класс, реализующий такой интерфейс не должен реализовывать член интерфейса с реализацией по умолчанию.
