Search code examples
c#clonecovariancecontravariance

Is it possible to implement the "virtual constructor" pattern in C# without casts?


I'm writing a program that writes C# that eventually gets compiled into an application. I would like each of the generated types to provide a "deep clone" function which copies the entire tree of data. That is, I want someone to be able to do:

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

instead of

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();

However, C# doesn't allow you to do this in a simple override; the overrides must return the same type as the declared type on the base.

Since I'm writing code that stamps out boilerplate anyway, is there something I can generate to allow the first block to compile? I tried something similar to the following:

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);
    }
}

but this does not compile because the overrides don't match. I also tried overriding the method from the base and hiding it with the "new" keyword, but this didn't work either.


Solution

  • Yes it is doable, but you must move your abstract method from being public to being protected then make a public non abstract function that just calls the protected method. The derived classes just need to implement the protected function and can shadow the public function, performing the cast that would have been performed by the client.

    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);
        }
    }