How to override method with derived return type in C#?

和自甴很熟 提交于 2019-12-20 06:28:12

问题


I want to override a virtual method with a derived class type. What's the current best way to do this? So far I've found two approaches:

  1. Use an abstract base class for each derived type; bridge with protected methods.
  2. Use a protected implementation with a public accessor.

Base case (no solution implemented, Clone always returns base type A1):

    public class A1
    {
        public int X1 { get; set; }
        public A1(int x1) { this.X1 = x1; }
        public virtual A1 Clone() { return new A1(X1); }
    }
    public class A2 : A1
    {
        public int X2 { get; set; }
        public A2(int x1, int x2) : base(x1)  { this.X2 = x2; }
        public override A1 Clone() { return new A2(X1, X2); }  //can't explicitly return A2
    }
    public class A3 : A2
    {
        public int X3 { get; set; }
        public A3(int x1, int x2, int x3) : base(x1, x2) { this.X3 = x3; }
        public override A1 Clone() { return new A3(X1, X2, X3); }  //can't explicitly return A3
    }

Solution #1 (using abstract base classes for each derived type with protected bridges):

    public class B1
    {
        public int X1 { get; set; }
        public B1(int x1) { this.X1 = x1; }
        public virtual B1 Clone() { return new B1(X1); }
    }
    public abstract class B2_Base : B1
    {
        public B2_Base(int x1) : base(x1) { }
        public sealed override B1 Clone() { return this.CloneAsB1(); }
        protected abstract B1 CloneAsB1();
    }
    public class B2 : B2_Base
    {
        public int X2 { get; set; }
        public B2(int x1, int x2) : base(x1) { this.X2 = x2; }
        protected sealed override B1 CloneAsB1() { return this.Clone(); }
        public new virtual B2 Clone() { return new B2(X1, X2); }  //CAN explicitly return B2
    }
    public abstract class B3_Base : B2
    {
        public B3_Base(int x1, int x2) : base(x1, x2) { }
        public sealed override B2 Clone() { return this.CloneAsB2(); }
        protected abstract B2 CloneAsB2();
    }
    public class B3 : B3_Base
    {
        public int X3 { get; set; }
        public B3(int x1, int x2, int x3) : base(x1, x2) { this.X3 = x3; }
        protected sealed override B2 CloneAsB2() { return this.Clone(); }
        public new virtual B3 Clone() { return new B3(X1, X2, X3); }  //CAN explicitly return B3
    }

Solution #2 (using protected implementation with public accessors):

    public class C1
    {
        public int X1 { get; set; }
        public C1(int x1) { this.X1 = x1; }
        public C1 Clone() { return this.CloneImplementation(); }
        protected virtual C1 CloneImplementation() { return new C1(X1); }
    }
    public class C2 : C1
    {
        public int X2 { get; set; }
        public C2(int x1, int x2) : base(x1) { this.X2 = x2; }
        public new C2 Clone() { return this.CloneImplementation() as C2; }  //trusts CloneImplementation to return a C2
        protected override C1 CloneImplementation() { return new C2(X1, X2); }
    }
    public class C3 : C2
    {
        public int X3 { get; set; }
        public C3(int x1, int x2, int x3) : base(x1, x2) { this.X3 = x3; }
        public new C3 Clone() { return this.CloneImplementation() as C3; }  //trusts CloneImplementation to return a C3
        protected override C1 CloneImplementation() { return new C3(X1, X2, X3); }
    }

So far as I can tell, Solution #1 is the most rigorous approach, but it requires an abstract base class for each derived class which wants to replace the base class's return type.

Solution #2's simpler and easier to understand, but it has a small break in internal type safety. Specifically, each derived type's public accessor trusts that its protected method will return the correct type. So it's possible to have an internal type disconnect, e.g.:

    public class C2 : C1
    {
        public int X2 { get; set; }
        public C2(int x1, int x2) : base(x1) { this.X2 = x2; }
        public new C2 Clone() { return this.CloneImplementation() as C2; }  //trusts CloneImplementation to return a C2
        protected override C1 CloneImplementation() { return new C1(X1); }
    }

Is there a correct (generally accepted) best practice for overriding methods with derived types?


回答1:


You could make the base class generic:

public abstract class Base<TDerived> where TDerived : Base {
  public abstract TDerived Clone();
}

public class Derived1 : Base<Derived1> {
  public override Derived1 Clone() { ... }
}

public class Derived2 : Base<Derived2> {
  public override Derived2 Clone() { ... }
}

However this makes me wonder how useful having a common base class is. Perhaps the Clone implementations of Derived1 and Derived2 don't need to be part of a common interface.




回答2:


The new keyword implicitly 'overrides' the base functionality anyway. Unless for some reason you specifically want override to appear in code, then a single new modifier will suffice. I would also explore abstrating the clone functionality into an interface, it allows you to make more assumptions in code, at a later date.

public interface ICloneable<out T>
{
    T Clone();
}

public class A1 : ICloneable<A1>
{
    public int X1 { get; set; }
    public A1(int x1) { this.X1 = x1; }

    public virtual A1 Clone()
    {
        return new A1(X1);
    }
}
public class A2 : A1, ICloneable<A2>
{
    public int X2 { get; set; }

    public A2(int x1, int x2)
        : base(x1)
    {
        this.X2 = x2;
    }

    public virtual new A2 Clone()
    {
        return new A2(X1, X2);
    }
}

public class A3 : A2, ICloneable<A3>
{
    public int X3 { get; set; }

    public A3(int x1, int x2, int x3)
        : base(x1, x2)
    {
        this.X3 = x3;
    }

    public virtual new A3 Clone()
    {
        return new A3(X1, X2, X3);
    }
}

EDIT: The resultant possible behaviour:

public class A4 : A3, ICloneable<A4>
{
    public int X4 { get; set; }

    public A4(int x1, int x2, int x3, int x4)
        : base(x1, x2, x3)
    {
        this.X4 = x4;
    }

    public override A3 Clone()
    {
        return ((ICloneable<A4>)this).Clone();
    }

    A4 ICloneable<A4>.Clone()
    {
        return new A4(X1, X2, X3, X4);
    }
}



回答3:


I'd advise against all of this. Just stick to the standard interfaces and patterns for such things. Implement System.ICloneable...

http://msdn.microsoft.com/en-us/library/system.icloneable(v=vs.110).aspx

Object Clone()

Simple no?

If you must deviate, I would use generics as Andrew Kennan has suggested. However I would still implement System.ICloneable as it makes the class more inter-operable with other frameworks.

In addition ICloneable should be implemented using a protected constructor e.g.

public class A1 : ICloneable
{
    public A1(int x1) { this.X1 = x1; }
    protected A1(A1 copy) { this.X1 = copy.X1; }

    public int X1 { get; set; }

    public virtual object Clone()
    {
        return new A1(this); // Protected copy constructor
    }
}

This way you can inherit A1 as such...

public class B1 : A1, ICloneable
{
    public B1(int x1, int y1) : base(x1) { this.Y1 = y1; }
    protected B1(B1 copy) : base(copy) { this.Y1 = copy.Y1; }

    public int Y1 { get; set; }

    public virtual object Clone()
    {
        return new B1(this); // Protected copy constructor
    }
}


来源:https://stackoverflow.com/questions/22313097/how-to-override-method-with-derived-return-type-in-c

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!