Обложка канала

.NET Разработчик

Опытный разработчик не так давно зашёл в .Net и поставил цель получить сертификат Microsoft. Свой ежедневный прогресс он описывает на канале .Net Разработчик. Заметки об изученном материале, советы по повышению производительности и поддержке мотивации, ин

.NET Разработчик

4 года назад
Открыть в
День 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