Может ли метод базового класса вернуть это, даже в производном классе? - PullRequest
9 голосов
/ 17 октября 2011

Я хотел бы иметь возможность иметь метод в базовом классе C #, который можно вызывать для объектов нескольких производных классов, который возвращает сам объект, и чтобы CLR знал, какой тип объекта на самом деле - , т.е. , соответствующий производный тип. Может кто-нибудь предложить способ сделать это? Конечно, кроме ковариации возвращаемого типа, которой нет у C #.

Примерно так, за исключением того, что тип возвращаемого значения Method() должен быть типом производного класса, а не базовым:

public abstract class Base { 
    public Base Method() { return this; }
}

public class Derived1: Base { ... }

public class Derived2: Base { ... }

public class Main {
    public static int Main() {
        Derived1 d1 = new Derived1();
        Derived1 x = d1.Method();
        Derived2 d2 = new Derived2();
        Derived2 y = d2.Method();
    }
}

Я могу думать только о двух способах заставить это работать, и мне не нравится ни один из них:

  1. Приведите результат метода () к ожидаемому типу ( например, , Derived1 x = (Derived) d1.Method();). Но приведения являются инструментом дьявола, и, кроме того, цель метода - вернуть Derived1 или Derived2 или ..., а не Base.

  2. Объявите Method() как абстрактное в базе и реализуйте его отдельно в каждом производном классе. Но это противоречит идее выделения общих методов. Method() будет идентичным в каждом случае, кроме его типа возврата.

Ответы [ 7 ]

12 голосов
/ 17 октября 2011

Полагаю, вы можете использовать ключевое слово dynamic в C # 4.0:

public abstract class Base { 
    public dynamic Method() { return this; }
}

public class Derived1: Base { ... }

public class Derived2: Base { ... }

public class Main {
    public static int Main() {
        Derived1 d1 = new Derived1();
        Derived1 x = d1.Method();
        Console.WriteLine(x.GetType()); // outputs Derived1
        Derived2 d2 = new Derived2();
        Derived2 y = d2.Method();
        Console.WriteLine(y.GetType()); // outputs Derived2
    }
}
11 голосов
/ 17 октября 2011

Хорошо, я неправильно понял вопрос. Я думал, что OP действительно хочет переопределить метод. Очевидно, нет, поэтому дженерики - это путь вперед:

public abstract class Base<T> where T : Base<T>
{
    public T Method()
    {
        return (T) (object) this;
    }
}

public class Derived1 : Base<Derived1>
{
}

Есть еще актеры, но, к сожалению, это неизбежно, насколько я знаю. Вы почти наверняка захотите проверить это и в конструкторе:

public Base()
{
    if (this as T == null)
    {
        // throw some exception
    }
}

Это ужасно, но это сработает ... и безобразие ограничено базовым классом.


Оригинальный ответ

Один из способов сделать это - поместить Method в общий интерфейс и реализовать его явно:

public interface IFoo<T> {
    T Method();
}

public abstract class Base : IFoo<Base>
{
    Base IFoo<Base>.Method()
    {
        return this;
    }
}

public class Derived1 : IFoo<Derived1>
{
    public Derived1 Method()
    {
        // If you need to call the base version, you'll
        // need ((IFoo<Base>)this).Method()
        return this;
    }
}

Это нехорошо, но это сработало бы ... там, где это возможно, я думаю, я бы старался избегать этого, если честно. (И да, я сталкивался с подобными ситуациями при реализации буферов протокола. Это раздражает.)

8 голосов
/ 17 октября 2011

Кажется, есть много способов сделать это: Также возможно делать с методами расширения.
Преимущество этого состоит в том, что вы можете использовать классы, которые вам не принадлежат, и вам придется делать это только один раз, а не один раз для каждого производного / базового класса. Это сделает именно то, что вы ищете.

public class BaseClass
{

}
public class DerivedClass: BaseClass
{

}
public static class BaseClassHelpers
{
    public static T Method<T>(this T b) where T : BaseClass
    {
        return b;
    }
}    

используется:

DerivedClass d = new DerivedClass();
DerivedClass dd = d.Method();
Console.WriteLine(dd.GetType());

результат в консоли:

DerivedClass
5 голосов
/ 17 октября 2011

Также с дженериками, но без интерфейса:

public abstract class Base<T> where T : Base<T>
{
    public virtual T Method()
    {
        return (T) this;
    }
}

public class Derived1 : Base<Derived1>
{
    public override Derived1 Method()
    {
        return base.Method();
    }
}

public class Derived2: Base<Derived2> { }

public class Program {
public static int Main() {
    Derived1 d1 = new Derived1();
    Derived1 x = d1.Method();
    Derived2 d2 = new Derived2();
    Derived2 y = d2.Method();
    return 0;
}
1 голос
/ 23 декабря 2016

Если ваш метод имеет параметры типа T, то явные общие аргументы не нужны.

public class Base
{
    public T Method<T>()where T: Base
    {
        return (T)this;
    }
}

public class Derived1 : Base { }
public class Derived2 : Base { }

Derived1 d1 = new Derived1();
Derived1 x = d1.Method<Derived1>();
Derived2 d2 = new Derived2();
Derived2 y = d2.Method<Derived2>();
1 голос
/ 13 августа 2013

Это решение без приведения

public abstract class Base<T> where T : Base<T>
{
    public T Method()
    {
        return ThisDerived;
    }

    // It is worth to do this if you need the "this" of the derived class often in the base class
    protected abstract T ThisDerived { get; }
}

public class Derived1 : Base<Derived1>
{
    protected override Derived1 ThisDerived{ get { return this; } } 
}

public class Derived2 : Base<Derived2>
{
    protected override Derived2 ThisDerived { get { return this; } }
}
0 голосов
/ 13 августа 2013

Существует очень простой способ достижения желаемого результата с помощью ключевого слова new и скрытой подписи:

class Base
{
    public virtual Base Method () { return this ; }
}

class Derived1: Base 
{ 
    public new Derived1 Method () { return this ; }
}

class Derived2: Base
{
    public new Derived2 Method () { return this ; }
}
...