10

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.

Billy ONeal
  • 104,103
  • 58
  • 317
  • 552
  • 1
    There's no way in C# for a class to generically refer to its own type unfortunately. There's hacky ways like the curiously recurring template pattern, which saves you the boilerplate work but isn't foolproof. – Asad Saeeduddin May 06 '15 at 17:17
  • @Asad: There are no generics involved here. – Billy ONeal May 06 '15 at 17:17
  • "There's no generics involved here" Yes, but this problem is conceptually one of generics. You want a `DeepClone` method that can accept some arbitrary type derived from `Base`, perform some operations on it, and return the same type. – Asad Saeeduddin May 06 '15 at 17:19
  • @Asad: No, I want `DeepClone` to be a non generic member. – Billy ONeal May 06 '15 at 17:23
  • @Scott: Unfortunately this is being used with things like serializers that require classes rather than interfaces. (If it was an interface, I could just explicitly implement it) – Billy ONeal May 06 '15 at 17:24
  • @BillyONeal But are you okay with the class being generic? Here is basically what I am suggesting `abstract class Base where T: Base Base { public abstract T DeepClone(); }`. Then: `class Base2 : Base { public override Base2 DeepClone() { ... } }` – Asad Saeeduddin May 06 '15 at 17:25
  • @Asad: No, that doesn't work because to name the base type you'd need something like `Base>>>` which is not namable. If someone only has a reference to a Base, they should get a reference to Base back. – Billy ONeal May 06 '15 at 17:27
  • @Asad shouldn't that constraint be `where T : Base`? – juharr May 06 '15 at 17:27
  • @BillyONeal Code is worth a thousand words I guess: http://ideone.com/Q4YS9w – Asad Saeeduddin May 06 '15 at 17:30
  • @juharr Yes, sorry. I got confused by the inline code formatting. – Asad Saeeduddin May 06 '15 at 17:30
  • @Asad: That doesn't work because you can't pass around references to `Base`. – Billy ONeal May 06 '15 at 17:30
  • @Asad But then `Derived : Base2` would return a `Base2` unless you made `Base2` generic. I think that might be what Billy is getting at. – juharr May 06 '15 at 17:32
  • @BillyONeal Yeah, but you can put the non generic version above the generic one. See http://ideone.com/Q4YS9w – Asad Saeeduddin May 06 '15 at 17:35
  • @juharr Yes, that's true. The interface becomes a little less convenient if you have a deep hierarchy, but you can always have nongeneric methods that return specific generic types, eg. Base.MakeBase2(). – Asad Saeeduddin May 06 '15 at 17:42
  • Does `Base` have other stuff in it or just `DeepClone`? – Asad Saeeduddin May 06 '15 at 17:56
  • @Asad: Base has other stuff in it. – Billy ONeal May 06 '15 at 17:57
  • @BillyONeal Would it be okay to separate the cloning from the `Base` functionality? Things would become a lot clearer if you had an interface called `IDeepCloneable` that had a `T DeepClone()` member. You could explicitly implement this on all your derived classes (`IDeepCloneable`, `IDeepCloneable`, etc...) and your class inheritance hierarchy would remain unmolested. – Asad Saeeduddin May 06 '15 at 18:00

2 Answers2

4

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);
    }
}
Scott Chamberlain
  • 124,994
  • 33
  • 282
  • 431
  • 1
    All computer science problems can be solved by adding a layer of indirection, eh? :) – Billy ONeal May 06 '15 at 17:45
  • @BillyONeal You see this pattern all over the .NET framework in the newer classes (.NET 2.0+). For example [`BindinigList.AddNew(`](https://msdn.microsoft.com/en-us/library/ms132687(v=vs.110).aspx) and [`BindingList.AddNewCore`](https://msdn.microsoft.com/en-us/library/ms132688(v=vs.110).aspx) – Scott Chamberlain May 06 '15 at 19:42
3

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
}
Asad Saeeduddin
  • 46,193
  • 6
  • 90
  • 139
  • This pattern is called the [Curiously recurring template pattern](http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern) – Scott Chamberlain May 06 '15 at 17:39
  • @ScottChamberlain Yes, that's right. I mentioned this in the comments above. It's not foolproof in C#, but it saves you some typing. – Asad Saeeduddin May 06 '15 at 17:40