На 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 ответов:
как насчет общего базового класса?
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
предложение производного класса, в котором я гарантирую, что этот возвращает aRadioActivePoo
. Короче говоря, я бы сделал через дизайн по контракту то, что я не могу сделать через синтаксис.другие предпочитают подделка вместо этого. Это нормально, я думаю, но я склонен для экономии" инфраструктурных " строк кода. Если семантика кода достаточно ясна, я счастлив, и дизайн по контракту позволяет мне достичь этого, хотя это не механизм времени компиляции.
то же самое для дженериков, которые другие ответы предлагаю. Я бы использовал их по лучшей причине, чем просто возвращение радиоактивных какашек - но это только я.
Я знаю, что есть много решений для этой проблемы уже, но я думаю, что я придумал один, который исправляет проблемы, которые у меня были с существующими решениями.
Я не был счастлив с некоторыми из существующих решений по следующим причинам:
- первое решение Паоло Тедеско: кошка и Собака не имеют общего базового класса.
- второе решение Паоло Тедеско: это немного сложно и трудно читать.
- решение Даниэля Даранаса: это работает, но это будет загромождать ваш код с большим количеством ненужного литья и отладки.Утверждения 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
Я считаю, что ваш ответ называется ковариацией.
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
оказывает влияние на производительность, поэтому старайтесь избегать его.