День 1385. #ЗаметкиНаПолях #TipsAndTricks
Современные Приёмы в C#. Часть 1
Странно Повторяющийся Обобщённый Паттерн
Название предложено Стивеном Клири по аналогии со «Странно повторяющимся шаблоном» из C++.
Смысл паттерна в том, что интерфейс (или базовый тип) принимает в качестве параметра типа собственный производный тип:
interface IExample<TDerived>
{ … }
class MyExample : IExample<MyExample>
{ … }
Если интерфейс (или базовый тип) хочет использовать полноценный производный тип в качестве параметра метода или возвращаемого значения, то он может сам определить эти методы, не возлагая никакой ответственности на производный тип. Вот пример из .NET BCL:
public interface IEquatable<T>
{
bool Equals(T? other);
}
Использование:
sealed class MyEquatable :
IEquatable<MyEquatable>
{
public bool Equals(MyEquatable? other)
{ … }
}
MyEquatable.Equals реализует IEquatable<T>.Equals со строго типизированным аргументом MyEquatable. Без использования паттерна пришлось бы использовать IEquatable (принимая object) и терялась бы безопасность типов и эффективность.
Интерфейс (или базовый тип) также может использовать себя в качестве ограничения обобщения. Иногда это полезно, особенно для базовых типов:
abstract class MyBase<TDerived>
where TDerived : MyBase<TDerived>
{
// Методы могут использовать (TDerived)this
// Полезно при возврате TDerived
public virtual TDerived Get()
=> (TDerived)this;
}
class Derived : MyBase<Derived>
{
// Неявно имеет реализацию
// public Derived Get(), т.к. метод
// базового класса правильно определяет
// тип возвращаемого значения.
// Но может и переопределить его
}
Например, это полезно в Fluent API. В более общем случае ограничение обобщения необходимо в любой из этих ситуаций:
- Базовый тип должен рассматривать экземпляр MyBase<TDerived> (например, this) как производный тип (т. е. (TDerived)this). Это также может возникнуть при передаче this другим методам.
- Базовый тип должен обрабатывать TDerived как MyBase<TDerived>, например, вызывая приватные базовые методы для экземпляра типа TDerived, отличного от this. В этом случае явное приведение не требуется.
Подобно обычным методам интерфейса, Curiously Recurring Generic Pattern может при необходимости повысить безопасность типов методов интерфейса по умолчанию. Это похоже на использование паттерна с базовым типом, за исключением того, что интерфейсы не могут иметь состояния. Иными словами, это позволяет использовать строго типизированные трейты, но не подходит для миксинов.
Как и другие паттерны кода, этот тоже можно использовать не по назначению. Наиболее распространённое неправильное использование – его чрезмерное применение. Кроме того, этот паттерн имеет усложняет код. Здесь нужно найти компромисс между добавляемой ценностью и усложнением поддержки кодовой базы.
Источник: https://blog.stephencleary.com/2022/09/modern-csharp-techniques-1-curiously-recurring-generic-pattern.html