TGViewer
.NET Разработчик .NET Разработчик @netdeveloperdiary · 6.77K subscribers
Post #1716 1.5K
День 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
  • 👍 14
More from @netdeveloperdiary
  1. Oct 10, 2026День 2810. #ЧтоНовенького #VSCode Более Быстрый и Лёгкий C# Dev Kit Мы, разработчики, люби…
  2. Oct 9, 2026День 2809. #Карьера 5 Навыков, Которые Помогут Быстрее Стать Сеньором. Окончание Начало 3.…
  3. Oct 8, 2026День 2808. #Карьера 5 Навыков, Которые Помогут Быстрее Стать Сеньором. Начало В ИТ есть се…
  4. Oct 7, 2026День 2807. #ЗаметкиНаПолях Типы Коллекций в .NET, Которые Стоит Попробовать. Окончание Нач…
  5. Oct 6, 2026🦈 Открытое собеседование на Middle C# | 6 октября, 19:00 МСК Приглашаем на открытое собес…
  6. Oct 6, 2026День 2806. #ЗаметкиНаПолях Типы Коллекций в .NET, Которые Стоит Попробовать. Начало Больши…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →