Как написать хороший любопытно повторяющийся шаблон шаблона (CRTP) в С#

В то время как назад я хотел создать свой собственный картограф данных, который был бы намного проще, чем ваш средний ORM. При этом я нашел необходимость иметь доступ к типу информации о наследовании классов в базовом классе. Моя первая мысль была отражением, но она слишком медленная (если вы используете рефлексию, проверьте Fasterflect, поскольку она почти "устраняет проблемы с производительностью отражения" ).

Итак, я обратился к решению, которое позже я узнал, если у него есть собственное имя: Curiously Recurring Template Pattern. Это в основном решило мою проблему, но научиться правильно реализовывать эту схему было немного сложной задачей. Два основных вопроса, которые я должен был решить:

1) Как я могу позволить моему потребительскому коду работать с моими общими объектами без необходимости знать общие параметры, с которыми были созданы объекты?

2) Как я могу наследовать статические поля в С#?

Сложная часть на самом деле выясняла вопросы. Как только я понял, что мне нужно сделать, решение этих вопросов было довольно простым. Если вы нуждаетесь в CRTP, вам, скорее всего, придется отвечать на эти вопросы... они, похоже, идут рука об руку.

Ответ 1

Работа с дженериками без знания общих типов параметров

При использовании CRTP полезно иметь неосновный базовый класс (абстрактно, если это возможно, но это не так уж важно), наследуемый вашим базовым классом. Затем вы можете создавать абстрактные (или виртуальные) функции в своем базовом классе и не допускать, чтобы потребительский код работал с вашими объектами, не зная общих параметров. Например:

abstract class NonGenBase
{
    public abstract void Foo();
}

class GenBase<T>: NonGenBase
{
    public override void Foo()
    {
        // Do something
    }
}

Теперь потребляющий код, который не знает, что T должен быть, может по-прежнему вызывать процедуру Foo() на ваших объектах, рассматривая их как экземпляры базового класса.

Как решить задачу наследования статического поля

При использовании CRTP для решения проблемы часто полезно предоставлять доступ к статическим полям при наследовании классов. Проблема в том, что С# не позволяет наследовать классы, чтобы иметь доступ к этим статическим полям, кроме как через имя типа... которое часто, кажется, побеждает цель в этой ситуации. Возможно, вы не сможете придумать ясный пример того, о чем я говорю, и объяснение того, что это выходит за рамки этого ответа, но решение прост, поэтому просто уберите его в своей базе знаний и когда вы найдете в нем потребность вы будете этому рады:)

class GenBase<T>: NonGenBase
{
    static object _someResource;

    protected object SomeResource { get { return _someResource; } }
}

Это "имитирует" наследование статических полей. Однако имейте в виду, что статические поля в общем классе не охвачены всеми вашими универсальными реализациями. Каждая родовая реализация имеет свой собственный экземпляр статического поля. Если вы хотите, чтобы одно статическое поле было доступно для всех реализаций, вам просто нужно добавить его в свой не общий базовый класс.