Явная реализация интерфейса и реализация по умолчанию

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

Чтобы явно реализовать интерфейс мы должны придерживаться следующих правил:

  1. реализуемый член интерфейса не должен иметь никаких модификаторов доступа
  2. перед именем члена через точку указывается имя интерфейса

Теперь запустим приложение и убедимся, что используются оба метода:

Денег в кармане 100
Денег в кошельке 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}");

в консоли мы увидим вот такой вывод:

Id = b324cfba-2a87-476d-a78f-b9e6ae78d91a; Имя Вася Пупкин

Если теперь мы реализуем в нашем классе точно такое же свойство, как и у интерфейса, то именно это свойство будет использоваться объектом. Например:

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}");
Id = abcd; Имя Вася Пупкин

Таким образом, реализация интерфейса по умолчанию позволяет расширить функциональность интерфейса, не прибегая при этом, к реализации новых его членах во всех классах, реализующих этот интерфейс.

Итого

Явная реализация интерфейса — это член класса, который вызывается только через указанный интерфейс. Использование явной реализации позволяет использовать члены различных интерфейсов, имеющих одну и ту же сигнатуру. Начиная с C# 8.0 можно создавать реализации по умолчанию различных членов интерфейса непосредственно в самом интерфейсе. Класс, реализующий такой интерфейс не должен реализовывать член интерфейса с реализацией по умолчанию.

Подписаться
Уведомить о
guest
0 Комментарий
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии