При множественной реализации интерфейсов мы можем столкнуться с таким поведением: если класс реализует несколько интерфейсов, содержащих член с одинаковой сигнатурой (например, метод), то при реализации такого члена в классе оба интерфейса будут использовать этот член в качестве собственной реализации. Чтобы избежать такого поведения, нам потребуется явная реализация интерфейса. Более того, если в нашем приложении будет большое количество различных классов, реализующих один и тот же интерфейс, то нам необходимо будет реализовать все члены этого интерфейса в каждом классе. Хоть это и не является проблемой, как таковой (это обычная работа с интерфейсами в 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 можно создавать реализации по умолчанию различных членов интерфейса непосредственно в самом интерфейсе. Класс, реализующий такой интерфейс не должен реализовывать член интерфейса с реализацией по умолчанию.