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

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

  1. Используйте abstract базу class для каждого производного типа; мост с помощью protected методов.
  2. Используйте 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 аксессорами):

    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 является наиболее строгим подходом, но для него требуется abstract базовый class для каждого производного class, который хочет заменить возвращаемый базовый class тип.

Решение №2 проще и понятнее, но в нем есть небольшой перерыв во внутренней безопасности типов. В частности, метод доступа public каждого производного типа полагает, что его метод 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); }
    }

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


person Nat    schedule 10.03.2014    source источник
comment
Это в значительной степени вопрос о пригодности и пригодности для решения типа цели, который в основном определяется мнением. Тем не менее, обычно используется первый подход, когда возвращаемое значение не изменяется. Но это не обязательно.   -  person LB2    schedule 11.03.2014


Ответы (3)


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

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

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

person Andrew Kennan    schedule 10.03.2014
comment
Решение №3, спасибо! Можем ли мы адаптировать его к тому, где Derived2 наследуется от Derived1? - person Nat; 11.03.2014
comment
Проблема с наследованием Derived2 от Derived1 таким образом заключается в том, что Derived1 тогда должен будет иметь возможность принимать параметр типа. Я не совсем уверен, что метод Clone, изменяющий тип возвращаемого значения, настолько полезен. Метод, определенный абстрактным базовым классом, обычно вызывается из контекста, который не знает производного типа. Имея подпись изменения клона, вы говорите, что Derived1.Clone вызывается только из кода, который уже знает, что имеет дело с Derived1, так какой смысл объявлять его в базовом классе? - person Andrew Kennan; 11.03.2014

Ключевое слово 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);
    }
}
person Xenolightning    schedule 10.03.2014
comment
Небольшое изменение к этому, делая Clone() реализации виртуальными, что позволяет override в подклассе. - person Xenolightning; 11.03.2014
comment
Я настоятельно рекомендую не использовать любое решение, в котором используется ключевое слово new. При создании экземпляра класса, такого как A3, определение клона будет меняться в зависимости от того, как на этот экземпляр ссылаются. Это очень опасно - person Mick; 11.03.2014
comment
@Mick Я согласен. Использование модификатора new приводит к неожиданному поведению при использовании ссылочных типов. A2 expected_a3_but_got_a2 = (new A3(..) as A2).Clone(). Однако у этой проблемы нет "хорошего" решения, без приведения типов после Clone(), и обобщения не работают с несколькими уровнями наследования. - person Xenolightning; 11.03.2014

Я бы не советовал всего этого. Просто придерживайтесь стандартных интерфейсов и шаблонов для таких вещей. Внедрить 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
    }
}
person Mick    schedule 11.03.2014
comment
Clone использовался в качестве простого примера, но этот вопрос не ограничивается методами клонирования. Тем не менее, вы не против использовать B1.Clone() as B1? - person Nat; 11.03.2014
comment
Когда вы смотрите на использование иерархий наследования для решения проблемы, часто лучше разложить проблемную область на более мелкие классы, которые могут работать друг с другом через ассоциации агрегации и композиции. Это также помогает использовать преимущества шаблонов IOC и фреймворков для инъекций. Обычно в арсенале OO Architects наследование не является предпочтительным оружием. - person Mick; 11.03.2014