Подтвердить что ты не робот

С#: переопределение типов возврата

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

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

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

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

4b9b3361

Ответ 1

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

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

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

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

Ответ 2

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

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

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

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

Ответ 3

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

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

  • Первое решение Паоло Тедеско: у кошки и собаки нет общего базового класса.
  • Второе решение Паоло Тедеско: Это немного сложно и трудно читать.
  • Решение Даниэля Даранаса: Это работает, но это загромождает ваш код множеством ненужных приведений и операторов 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".
  • Out Generic Modifier (Ковариантность). Опять же, я не использовал его в этом решении, но если вы хотите сделать что-то вроде возврата MyType<Poo> из IAnimal и возврата MyType<PooType> из BaseAnimal, вам нужно будет использовать его для приведения между ними.

Ответ 4

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

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

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

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

Ответ 5

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

В следующем примере я делаю "Excrement" не виртуальным, но предоставляю свойство ExcrementImpl, чтобы позволить производным классам предоставлять правильный "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; } }
}
}

Ответ 6

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

Ответ 7

Попробуйте следующее:

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

Ответ 8

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

Ответ 9

Это может помочь, если RadioactivePoo получен из poo, а затем использует generics.

Ответ 10

FYI. Это довольно легко реализовано в 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

Ответ 11

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

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

Ответ 12

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

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

Ответ 13

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