C #: переопределение типов возврата - PullRequest
73 голосов
/ 26 июня 2009

Есть ли способ переопределить типы возврата в C #? Если да, то как, а если нет, то почему и как это рекомендуется делать?

В моем случае у меня есть интерфейс с абстрактным базовым классом и его потомками. Я хотел бы сделать это (хорошо, не совсем, но в качестве примера!):

public interface Animal
{
   Poo Excrement { get; }
}

public class AnimalBase
{
   public virtual Poo Excrement { get { return new Poo(); } }
}

public class Dog
{
  // No override, just return normal poo like normal animal
}

public class Cat
{
  public override RadioactivePoo Excrement { get { return new RadioActivePoo(); } }
}

RadioactivePoo конечно, наследуется от Poo.

Моя причина для этого заключается в том, чтобы те, кто использует объекты Cat, могли использовать свойство Excrement, не приводя Poo к RadioactivePoo, в то время как, например, Cat все еще может быть частью Animal список, в котором пользователи могут не знать или заботиться о своем радиоактивном состоянии. Надеюсь, что это имеет смысл ...

Насколько я вижу, компилятор не позволяет этого по крайней мере. Так что я думаю, что это невозможно. Но что бы вы порекомендовали в качестве решения этой проблемы?

Ответы [ 13 ]

46 голосов
/ 26 июня 2009

А как насчет базового базового класса?

public class Poo { }
public class RadioactivePoo : Poo { }

public class BaseAnimal<PooType> 
    where PooType : Poo, new() {
    PooType Excrement {
        get { return new PooType(); }
    }
}

public class Dog : BaseAnimal<Poo> { }
public class Cat : BaseAnimal<RadioactivePoo> { }

РЕДАКТИРОВАТЬ : новое решение, использующее методы расширения и интерфейс маркера ...

public class Poo { }
public class RadioactivePoo : Poo { }

// just a marker interface, to get the poo type
public interface IPooProvider<PooType> { }

// Extension method to get the correct type of excrement
public static class IPooProviderExtension {
    public static PooType StronglyTypedExcrement<PooType>(
        this IPooProvider<PooType> iPooProvider) 
        where PooType : Poo {
        BaseAnimal animal = iPooProvider as BaseAnimal;
        if (null == animal) {
            throw new InvalidArgumentException("iPooProvider must be a BaseAnimal.");
        }
        return (PooType)animal.Excrement;
    }
}

public class BaseAnimal {
    public virtual Poo Excrement {
        get { return new Poo(); }
    }
}

public class Dog : BaseAnimal, IPooProvider<Poo> { }

public class Cat : BaseAnimal, IPooProvider<RadioactivePoo> {
    public override Poo Excrement {
        get { return new RadioactivePoo(); }
    }
}

class Program { 
    static void Main(string[] args) {
        Dog dog = new Dog();
        Poo dogPoo = dog.Excrement;

        Cat cat = new Cat();
        RadioactivePoo catPoo = cat.StronglyTypedExcrement();
    }
}

Таким образом, Dog и Cat наследуются от Animal (как отмечалось в комментариях, мое первое решение не сохранило наследство).
Необходимо явно пометить классы интерфейсом маркера, что является болезненным, но, возможно, это может дать вам некоторые идеи ...

ВТОРОЕ РЕДАКТИРОВАНИЕ @Svish: я изменил код, чтобы ясно показать, что метод расширения никоим образом не обеспечивает факт, что iPooProvider наследуется от BaseAnimal. Что вы подразумеваете под «еще более типизированным»?

30 голосов
/ 26 июня 2009

Это называется ковариация возвращаемого типа и не поддерживается в C # или .NET в целом, несмотря на пожелания некоторых людей .

Что бы я сделал, это сохранитьта же самая подпись, но добавьте дополнительное предложение ENSURE к производному классу, в котором я гарантирую, что это возвращает RadioActivePoo.Итак, короче говоря, я бы делал через дизайн по контракту то, что я не могу сделать через синтаксис.

Другие предпочитают вместо этого подделать .Это нормально, я думаю, но я склонен экономить строки инфраструктуры.Если семантика кода достаточно ясна, я счастлив, и дизайн по контракту позволяет мне достичь этого, хотя это не механизм времени компиляции.

То же самое для обобщений, которые другие ответы предложить.Я хотел бы использовать их по лучшей причине, чем просто вернуть радиоактивный какашка - но это только я.

18 голосов
/ 05 декабря 2012

Я знаю, что уже есть много решений для этой проблемы, но я думаю, что нашел решение, которое решает проблемы, которые у меня были с существующими решениями.

Мне не понравились некоторые из существующих решений по следующим причинам:

  • Первое решение Паоло Тедеско: Кошка и собака не имеют общего базового класса.
  • Второе решение Паоло Тедеско: Это немного сложно и трудно для чтения.
  • Решение Даниэля Даранаса: Это работает, но оно загромождает ваш код множеством ненужных приведений и операторов Debug.Assert ().
  • hjb417: Это решение не позволяет хранить логику в базовом классе. Логика в этом примере довольно тривиальна (вызов конструктора), но в реальном примере это не так.

Мое решение

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

public class Poo { }
public class RadioactivePoo : Poo { }

interface IAnimal
{
    Poo Excrement { get; }
}

public class BaseAnimal<PooType> : IAnimal
    where PooType : Poo, new()
{
    Poo IAnimal.Excrement { get { return (Poo)this.Excrement; } }

    public PooType Excrement
    {
        get { return new PooType(); }
    }
}

public class Dog : BaseAnimal<Poo> { }
public class Cat : BaseAnimal<RadioactivePoo> { }

С этим решением вам не нужно ничего переопределять в Dog ИЛИ Cat! Вот пример использования:

Cat bruce = new Cat();
IAnimal bruceAsAnimal = bruce as IAnimal;
Console.WriteLine(bruce.Excrement.ToString());
Console.WriteLine(bruceAsAnimal.Excrement.ToString());

Это выдаст дважды «RadioactivePoo», что показывает, что полиморфизм не нарушен.

Дополнительная литература

  • Явная реализация интерфейса
  • новый модификатор . Я не использовал его в этом упрощенном решении, но вам может понадобиться более сложное решение. Например, если вы хотите создать интерфейс для BaseAnimal, вам нужно будет использовать его при объявлении «PooType Excrement».
  • из общего модификатора (ковариация) . Опять же, я не использовал его в этом решении, но если вы хотите сделать что-то вроде возврата MyType<Poo> из IAnimal и возврата MyType<PooType> из BaseAnimal, то вам нужно использовать его для возможности приведения между ними.
9 голосов
/ 26 июня 2009

Существует также эта опция (явная реализация интерфейса)

public class Cat:Animal
{
  Poo Animal.Excrement { get { return Excrement; } }
  public RadioactivePoo Excrement { get { return new RadioactivePoo(); } }
}

Вы теряете возможность использовать базовый класс для реализации Cat, но с положительной стороны вы сохраняете полиморфизм между Cat и Dog.

Но я сомневаюсь, что сложность того стоит.

4 голосов
/ 01 октября 2009

Почему бы не определить защищенный виртуальный метод, который создает «экскременты», и оставить публичное свойство, которое возвращает «экскремент», не виртуальным. Затем производные классы могут переопределять тип возврата базового класса.

В следующем примере я делаю 'Excrement' не виртуальным, но предоставляю свойство ExcrementImpl, чтобы позволить производным классам предоставлять правильный 'Poo'. Производные типы могут затем переопределить возвращаемый тип «Excrement», скрыв реализацию базового класса.

E.x:.

namepace ConsoleApplication8

{
public class Poo { }

public class RadioactivePoo : Poo { }

public interface Animal
{
    Poo Excrement { get; }
}

public class AnimalBase
{
    public Poo Excrement { get { return ExcrementImpl; } }

    protected virtual Poo ExcrementImpl
    {
        get { return new Poo(); }
    }
}

public class Dog : AnimalBase
{
    // No override, just return normal poo like normal animal
}

public class Cat : AnimalBase
{
    protected override Poo ExcrementImpl
    {
        get { return new RadioactivePoo(); }
    }

    public new RadioactivePoo Excrement { get { return (RadioactivePoo)ExcrementImpl; } }
}
}
2 голосов
/ 26 июня 2009

Попробуйте это:

namespace ClassLibrary1
{
    public interface Animal
    {   
        Poo Excrement { get; }
    }

    public class Poo
    {
    }

    public class RadioactivePoo
    {
    }

    public class AnimalBase<T>
    {   
        public virtual T Excrement
        { 
            get { return default(T); } 
        }
    }


    public class Dog : AnimalBase<Poo>
    {  
        // No override, just return normal poo like normal animal
    }

    public class Cat : AnimalBase<RadioactivePoo>
    {  
        public override RadioactivePoo Excrement 
        {
            get { return new RadioactivePoo(); } 
        }
    }
}
2 голосов
/ 26 июня 2009

Исправьте меня, если я не прав, но не весь смысл полиморфизма, чтобы иметь возможность вернуть RadioActivePoo, если он наследуется от Poo, контракт будет таким же, как абстрактный класс, но просто вернет RadioActivePoo ()

1 голос
/ 20 февраля 2010

Я думаю, что нашел способ, который не зависит от обобщений или методов расширения, а скорее от скрытия методов. Однако он может нарушить полиморфизм, поэтому будьте особенно осторожны, если вы продолжите наследовать от Cat.

Надеюсь, этот пост еще может кому-нибудь помочь, несмотря на опоздание на 8 месяцев.

public interface Animal
{
    Poo Excrement { get; }
}

public class Poo
{
}

public class RadioActivePoo : Poo
{
}

public class AnimalBase : Animal
{
    public virtual Poo Excrement { get { return new Poo(); } }
}

public class Dog : AnimalBase
{
    // No override, just return normal poo like normal animal
}

public class CatBase : AnimalBase
{
    public override Poo Excrement { get { return new RadioActivePoo(); } }
}

public class Cat : CatBase
{
    public new RadioActivePoo Excrement { get { return (RadioActivePoo) base.Excrement; } }
}
0 голосов
/ 02 мая 2017

Ну, на самом деле можно вернуть конкретный тип, который отличается от унаследованного возвращаемого типа (даже для статических методов), благодаря dynamic:

public abstract class DynamicBaseClass
{
    public static dynamic Get (int id) { throw new NotImplementedException(); }
}

public abstract class BaseClass : DynamicBaseClass
{
    public static new BaseClass Get (int id) { return new BaseClass(id); }
}

public abstract class DefinitiveClass : BaseClass
{
    public static new DefinitiveClass Get (int id) { return new DefinitiveClass(id);
}

public class Test
{
    public static void Main()
    {
        var testBase = BaseClass.Get(5);
        // No cast required, IntelliSense will even tell you
        // that var is of type DefinitiveClass
        var testDefinitive = DefinitiveClass.Get(10);
    }
}

Я реализовал это в API-оболочке, написанной для моей компании. Если вы планируете разработать API, это может улучшить удобство использования и опыт разработки в некоторых случаях. Тем не менее, использование dynamic влияет на производительность, поэтому старайтесь избегать его.

0 голосов
/ 29 мая 2015

Вы можете просто использовать возврат интерфейса. В твоем случае, IPoo.

В вашем случае это предпочтительнее использования универсального типа, потому что вы используете базовый класс комментариев.

...