Возможно ли реализовать шаблон "виртуального конструктора" в С# без приведения?

Я пишу программу, которая пишет С#, которая в конечном итоге скомпилируется в приложение. Я хотел бы, чтобы каждый из сгенерированных типов предоставлял функцию "глубокого клонирования", которая копирует все дерево данных. То есть, я хочу, чтобы кто-то мог:

var x = new Base(); // Base has public virtual Base DeepClone() { ... }
var y = new Derived(); // Derived overrides DeepClone
Base a = x.DeepClone();
Base b = y.DeepClone();
// Derived c = x.DeepClone(); // Should not compile
Derived d = y.DeepClone(); // Does not compile, DeepClone returns Base

вместо

var x = new Base();
var y = new Derived();
Base a = x.DeepClone();
Base b = y.DeepClone();
// Derived c = x.DeepClone(); // Should not compile
Derived d = (Derived)y.DeepClone();

Однако С# не позволяет вам сделать это в простой переопределении; переопределения должны возвращать тот же тип, что и объявленный тип на базе.

Так как я пишу код, который все равно печатает шаблон, есть ли что-то, что я могу создать, чтобы первый блок мог компилироваться? Я попробовал что-то похожее на следующее:

abstract class Base
{
    public abstract Base DeepClone();
}

class Base2 : Base
{
    int Member { get; set; }

    public Base2() { /* empty on purpose */ }
    public Base2(Base2 other)
    {
        this.Member = other.Member;
    }

    public override Base2 DeepClone()
    {
        return new Base2(this);
    }
}

sealed class Derived : Base2
{
    string Member2 { get; set; }

    public Derived() { /* empty on purpose */ }
    public Derived(Derived other)
        : base(other)
    {
        this.Member2 = other.Member2;
    }

    public override Derived DeepClone()
    {
        return new Derived(this);
    }
}

но это не компилируется, потому что переопределения не совпадают. Я также попытался переопределить метод из базы и скрыть его "новым" ключевым словом, но это тоже не сработало.

Ответ 1

Да, это выполнимо, но вы должны перенести свой абстрактный метод из публичного на то, чтобы быть защищенным, а затем сделать публичную не абстрактную функцию, которая просто вызывает защищенный метод. Производные классы просто нуждаются в реализации защищенной функции и могут теневать публичную функцию, выполняя приведение, которое было бы выполнено клиентом.

abstract class Base
{
    public Base DeepClone()
    {
        return CloneInternal();
    }

    protected abstract Base CloneInternal();
}

class Base2 : Base
{
    int Member { get; set; }

    public Base2() { /* empty on purpose */ }
    public Base2(Base2 other)
    {
        this.Member = other.Member;
    }

    new public Base2 DeepClone()
    {
        return (Base2)CloneInternal();
    }

    protected override Base CloneInternal()
    {
        return new Base2(this);
    }
}

sealed class Derived : Base2
{
    string Member2 { get; set; }

    public Derived() { /* empty on purpose */ }
    public Derived(Derived other)
        : base(other)
    {
        this.Member2 = other.Member2;
    }

    new public Derived DeepClone()
    {
        return (Derived)CloneInternal();
    }

    protected override Base CloneInternal()
    {
        return new Derived(this);
    }
}

Ответ 2

Вот способ сделать это, не связанный с кастингом. Это не позволяет вам сделать new Base() из вашего первого фрагмента, что не имеет никакого смысла, потому что оно абстрактно, но все работает:

interface Base
{
    Base DeepClone();
}

abstract class Base<T>: Base where T: Base<T>
{ 
    public abstract T DeepClone();
    Base Base.DeepClone() {
        return DeepClone();
    }
}

class Base2 : Base<Base2> 
{ 
    public override Base2 DeepClone() 
    {
        return new Base2();
    } 
}

Затем в вашем методе Main:

public static void Main()
{
    var y = new Base2(); // Base2 overrides DeepClone
    Base b = y.DeepClone();
    Base2 c = y.DeepClone(); // Compiles an works
}