На C#: переопределение возвращаемых типов


есть ли способ переопределить возвращаемые типы В 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 70

13 ответов:

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

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

EDIT: новое решение, использующее методы расширения и интерфейс маркера...

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

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

второй Редактировать @Svish: я изменил код, чтобы ясно показать, что метод расширения никоим образом не применяет тот факт, что iPooProvider наследует от BaseAnimal. Что вы подразумеваете под "еще более строго типизированный"?

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

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

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

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

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

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

  • первое решение Паоло Тедеско: кошка и Собака не имеют общего базового класса.
  • второе решение Паоло Тедеско: это немного сложно и трудно читать.
  • решение Даниэля Даранаса: это работает, но это будет загромождать ваш код с большим количеством ненужного литья и отладки.Утверждения 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> { }

С помощью этого решения вам не нужно ничего переопределять в собаке или кошке! Насколько это круто? Вот некоторые примеры использования:

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

это выведет: "RadioactivePoo" дважды, что показывает, что полиморфизм не был нарушен.

Более Дальнеишее Чтение

  • Явный Интерфейс Реализация
  • новый модификатор. Я не использовал его в этом упрощенном решении, но вам может понадобиться более сложное решение. Например, если вы хотите создать интерфейс для BaseAnimal, вам нужно будет использовать его в своем деклерации "экскрементов Путипа".
  • из общего модификатора (ковариация). Опять же, я не использовал его в этом решении, но если вы хотите сделать что-то вроде return MyType<Poo> из IAnimal и обратно MyType<PooType> от BaseAnimal тогда вам нужно будет использовать его, чтобы иметь возможность бросать между ними.

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

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

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

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

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

в следующем примере я делаю "экскременты" невиртуальными, но предоставляю свойство Excrumentimpl, чтобы производные классы могли предоставить правильный "Poo". Затем производные типы могут переопределять возвращаемый тип "экскрементов", скрывая базовый класс реализация.

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

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

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

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

Я думаю, что нашел способ, который не зависит от дженериков или методов расширения, а скорее скрывает метод. Однако он может нарушить полиморфизм, поэтому будьте особенно осторожны, если вы в дальнейшем унаследуете от 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; } }
}

к вашему сведению. Это довольно легко реализуется в Scala.

trait Path

trait Resource
{
    def copyTo(p: Path): Resource
}
class File extends Resource
{
    override def copyTo(p: Path): File = new File
    override def toString = "File"
}
class Directory extends Resource
{
    override def copyTo(p: Path): Directory = new Directory
    override def toString = "Directory"
}

val test: Resource = new Directory()
test.copyTo(null)

вот живой пример, с которым вы можете играть:http://www.scalakata.com/50d0d6e7e4b0a825d655e832

Это может помочь, если RadioactivePoo является производным от poo, а затем использовать дженерики.

Я считаю, что ваш ответ называется ковариацией.

class Program
{
    public class Poo
    {
        public virtual string Name { get{ return "Poo"; } }
    }

    public class RadioactivePoo : Poo
    {
        public override string Name { get { return "RadioactivePoo"; } }
        public string DecayPeriod { get { return "Long time"; } }
    }

    public interface IAnimal<out T> where T : Poo
    {
        T Excrement { get; }
    }

    public class Animal<T>:IAnimal<T> where T : Poo 
    {
        public T Excrement { get { return _excrement ?? (_excrement = (T) Activator.CreateInstance(typeof (T), new object[] {})); } } 
        private T _excrement;
    }

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

    static void Main(string[] args)
    {
        var dog = new Dog();
        var cat = new Cat();

        IAnimal<Poo> animal1 = dog;
        IAnimal<Poo> animal2 = cat;

        Poo dogPoo = dog.Excrement;
        //RadioactivePoo dogPoo2 = dog.Excrement; // Error, dog poo is not RadioactivePoo.

        Poo catPoo = cat.Excrement;
        RadioactivePoo catPoo2 = cat.Excrement;

        Poo animal1Poo = animal1.Excrement;
        Poo animal2Poo = animal2.Excrement;
        //RadioactivePoo animal2RadioactivePoo = animal2.Excrement; // Error, IAnimal<Poo> reference do not know better.


        Console.WriteLine("Dog poo name: {0}",dogPoo.Name);
        Console.WriteLine("Cat poo name: {0}, decay period: {1}" ,catPoo.Name, catPoo2.DecayPeriod);
        Console.WriteLine("Press any key");

        var key = Console.ReadKey();
    }
}

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

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

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