Вопрос

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

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

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

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

Это было полезно?

Решение

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

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

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

Другие советы

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

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

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

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

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

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

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

Есть еще такой вариант (явная реализация интерфейса)

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

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

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

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

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

Бывший.:

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

Может помочь, если RadioactivePoo будет получен из poo, а затем будет использовать дженерики.

К вашему сведению.В 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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top