Как переопределить метод с производным типом возвращаемого значения в С#? - программирование

Как переопределить метод с производным типом возвращаемого значения в С#?

Я хочу переопределить виртуальный метод с производным типом класса. Какой лучший способ сделать это сейчас? До сих пор я нашел два подхода:

  • Используйте abstract base class для каждого производного типа; мост с помощью методов protected.
  • Используйте реализацию protected с аксессуаром public.

Базовый регистр (не реализовано решение, Clone всегда возвращает базовый тип 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
    }

Решение № 1 (используя базовые классы abstract для каждого производного типа с мостами protected):

    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
    }

Решение №2 (с использованием реализации protected с помощью 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); }
    }

Насколько я могу судить, решение № 1 является самым строгим подходом, но для каждого производного class требуется база abstract class, которая хочет заменить базовый тип class.

Решение №2 проще и понятнее, но оно имеет небольшой разрыв во внутренней безопасности. В частности, каждый производный тип public accessor полагает, что его метод protected вернет правильный тип. Таким образом, существует возможность отключения внутреннего типа, например:

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

Есть ли правильная (общепринятая) наилучшая практика для переопределения методов с производными типами?

4b9b3361

Ответ 1

Вы можете сделать базовый класс общим:

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() { ... }
}

Однако это заставляет меня задаться вопросом, насколько полезно иметь общий базовый класс. Возможно, реализации Clone Derived1 и Derived2 не должны быть частью общего интерфейса.

Ответ 2

Ключевое слово new неявно "переопределяет" базовую функциональность. Если по какой-то причине вы специально не хотите, чтобы override отображался в коде, тогда достаточно одного модификатора new. Я бы также исследовал абстрагирование функциональности клона в интерфейсе, это позволяет вам делать больше предположений в коде позднее.

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

РЕДАКТИРОВАНИЕ: Возможное поведение:

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

Я бы посоветовал все это. Просто придерживайтесь стандартных интерфейсов и шаблонов для таких вещей. Внедрение System.ICloneable...

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

Object Clone()

Простой нет?

Если вы должны отклоняться, я бы использовал дженерики, как предложил Эндрю Кеннан. Однако я бы все же использовал System.ICloneable, поскольку он делает класс более совместимым с другими фреймворками.

Кроме того, ICloneable должен быть реализован с использованием защищенного конструктора, например.

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

Таким образом, вы можете наследовать A1 как таковой...

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