Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 839
Billy ONeal Avatar asked May 06 '15 17:05

Billy ONeal


2 Answers

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);
    }
}
like image 195
Scott Chamberlain Avatar answered Oct 06 '22 01:10

Scott Chamberlain


Here is a way to do this that doesn't involve any casting. It doesn't allow you to do new Base() from your first snippet, which doesn't make any sense because it is abstract, but the rest works:

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

Then, in your Main method:

public static void Main()
{
    var y = new Base2(); // Base2 overrides DeepClone
    Base b = y.DeepClone();
    Base2 c = y.DeepClone(); // Compiles an works
}
like image 33
Asad Saeeduddin Avatar answered Oct 06 '22 00:10

Asad Saeeduddin