Как применяются интерфейсы в C#?
Что проверяют
Понимаете ли вы интерфейс как контракт, по которому части системы связываются без знания реализации, и знаете ли, как интерфейсы изменились в современных версиях C#. Следом почти всегда просят сравнить с абстрактным классом.
Как отвечать
Интерфейс описывает методы, свойства, события и индексаторы, которые обязан реализовать тип. Класс наследуется только от одного класса, но реализует сколько угодно интерфейсов, структуры тоже.
Где это применяется на практике:
- внедрение зависимостей: в ASP.NET Core сервис регистрируется как
builder.Services.AddScoped<IOrderRepository, OrderRepository>(), а контроллер получаетIOrderRepositoryв конструкторе и не знает, что за ним: EF Core, Dapper или заглушка; - тестирование: зависимость по интерфейсу подменяется моком через Moq или NSubstitute;
- стандартные контракты платформы:
IDisposableиIAsyncDisposableдляusing,IEnumerable<T>дляforeachи LINQ,IComparable<T>иIEquatable<T>для сортировки и сравнения; - явная реализация:
void IPrinter.Print()решает конфликт одинаковых имён из двух интерфейсов и прячет член из публичного API класса.
Покажите, что знаете современные возможности. С C# 8 интерфейс может содержать реализацию по умолчанию: новый метод добавляется в интерфейс, не ломая существующие реализации. С C# 11 появились static abstract члены, на них построена generic math (INumber<T>). Ещё вариантность: IEnumerable<out T> ковариантен, поэтому IEnumerable<string> можно передать туда, где ждут IEnumerable<object>.
Отличие от абстрактного класса: у интерфейса нет полей экземпляра и конструкторов, общее состояние в нём не держат.
Где спотыкаются
Говорят, что интерфейс не может содержать реализации. Для современного .NET это устарело, хотя методы по умолчанию нужны для эволюции API, а не как замена базовому классу.
Заводят интерфейс на каждый класс «на всякий случай», даже когда реализация одна и подменять её незачем. Пары IFoo и Foo только удваивают навигацию.
Забывают про упаковку: структура, приведённая к интерфейсу, копируется в кучу. В горячем коде лучше обобщённое ограничение where T : IComparable<T>, оно упаковку обходит.