Описание тега default-interface-member
Члены интерфейса по умолчанию были введены в C#-8. Они похожи на функцию Java по умолчанию.
Член интерфейса теперь может быть указан с телом кода, и если реализующий класс или структура не предоставляет реализацию этого члена, ошибки не возникает. Вместо этого используется реализация по умолчанию.
Члены интерфейса по умолчанию помогают в следующих сценариях:
- Управление версиями интерфейса
- Взаимодействие с API для Android (Java) и iOS (Swift), которые поддерживают аналогичные функции.
- Для реализации трейтов без необходимости множественного наследования, как у трейтов PHP и Scala. Java 8 и более поздние версии также поддерживают черты с помощью методов интерфейса по умолчанию.
- Повторное использование кода в структурах (спасибо Эйрику Царпалису!)
Версии интерфейса
Этот пример адаптирован из статьи Мэдса Торгерсена о реализациях по умолчанию в интерфейсах:
Допустим, мы предлагаем следующий интерфейс:
interface ILogger
{
void Log(LogLevel level, string message);
}
И класс, реализующий это:
class ConsoleLogger : ILogger
{
public void Log(LogLevel level, string message) { ... }
}
С членами по умолчанию интерфейс может быть изменен без нарушения ConsoleLogger
:
interface ILogger
{
void Log(LogLevel level, string message);
void Log(Exception ex) => Log(LogLevel.Error, ex.ToString());
}
ConsoleLogger по-прежнему удовлетворяет контракту, предоставляемому интерфейсом: если он преобразован в интерфейс и вызывается новый метод Log, он будет работать нормально - просто вызывается реализация интерфейса по умолчанию:
public static void LogException(ConsoleLogger logger, Exception ex)
{
ILogger ilogger = logger; // Converting to interface
ilogger.Log(ex); // Calling new Log overload
}
Реализующий класс, который знает о новом члене, может реализовать его по-своему. В этом случае реализация по умолчанию просто игнорируется.
Черты
В воображаемой игре предметы могут унаследовать от GameItem
учебный класс:
public class GameItem { }
Предположим, у зелья нет места и оно не движется:
public class Potion:GameItem{}
У камня может быть местоположение:
public interface ILocatable
{
public (double x,double y) Location{get;set;}
}
public class Rock:GameItem,ILocatable
{
public (double x,double y) Location{get;set;}
}
Игрок или монстр также могут двигаться. Без черт одним из возможных решений было бы добавить возможность перехода кGameItem
или ввести промежуточный абстрактный класс с этой функциональностью. ИзменениеGameItem
также повлияет Rock
в то время как абстрактный класс представит отношение, которое, вероятно, не подходит.
Это можно решить с помощью IMovable
черта, которая может быть применена к любому типу, имеющему Location
свойство:
public interface IMovable
{
public abstract (double x,double y) Location{get;set;}
void Move(double angle,double speed)
{
var x=Location.x + speed*Math.Sin(angle);
var y=Location.y + speed*Math.Cos(angle);
Location=(x,y);
}
}
Эта черта может быть применена к любому классу, если у него есть соответствие Location
свойство:
public class Player:GameItem,ILocatable,IMovable
{
public (double x,double y) Location{get;set;}
}
public class Monster:GameItem,ILocatable,IMovable
{
public (double x,double y) Location{get;set;}
}
Пример трейта - чтение настроек в среде контейнера
В контейнерных или бессерверных приложениях одним из наиболее распространенных способов распространения настроек является использование переменных среды. Модули DIM можно использовать для создания признака, который извлекает конкретную переменную среды каждый раз, когда она вызывается, например:
interface IGithubSettings
{
public string CurrentToken => Environment.GetEnvironmentVariable("GitHubToken");
}
Ссылки:
- Учебник: обновление интерфейсов с использованием элементов интерфейса по умолчанию в C# 8.0
- Реализации по умолчанию в интерфейсах Мадса Торгерсена
- Запись в Википедии о чертах характера
- Предложение по методам интерфейса по умолчанию, включая мотивацию