Czy istnieje sposób na zastąpienie zwracanych typów w C #? Jeśli tak, to jak, a jeśli nie, dlaczego i jaki jest zalecany sposób?
Mój przypadek jest taki, że mam interfejs z abstrakcyjną klasą bazową i jej potomkami. Chciałbym to zrobić (ok niezupełnie, ale jako przykład!):
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
oczywiście dziedziczy z Poo
.
Powodem, dla którego chcę tego jest, aby ci, którzy używają Cat
przedmiotów, mogli korzystać z tej Excrement
właściwości bez konieczności rzucania Poo
do, RadioactivePoo
podczas gdy na przykład Cat
może nadal być częścią Animal
listy, na której użytkownicy niekoniecznie muszą być świadomi lub dbać o swoją radioaktywną kupę. Mam nadzieję, że to miało sens ...
O ile widzę, kompilator przynajmniej na to nie pozwala. Więc myślę, że to niemożliwe. Ale co byś polecił jako rozwiązanie tego problemu?
źródło
Odpowiedzi:
Wiem, że istnieje już wiele rozwiązań tego problemu, ale myślę, że wymyśliłem takie, które rozwiązuje problemy, które miałem z istniejącymi rozwiązaniami.
Nie byłem zadowolony z niektórych istniejących rozwiązań z następujących powodów:
Moje rozwiązanie
To rozwiązanie powinno przezwyciężyć wszystkie problemy, o których wspomniałem powyżej, stosując zarówno typy ogólne, jak i ukrywanie metod.
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> { }
Dzięki temu rozwiązaniu nie musisz niczego zastępować w Dog OR Cat! Oto kilka przykładowych zastosowań:
Cat bruce = new Cat(); IAnimal bruceAsAnimal = bruce as IAnimal; Console.WriteLine(bruce.Excrement.ToString()); Console.WriteLine(bruceAsAnimal.Excrement.ToString());
Spowoduje to dwukrotne wyświetlenie: „RadioactivePoo”, co oznacza, że polimorfizm nie został przerwany.
Dalsze czytanie
MyType<Poo>
z IAnimal i powrótMyType<PooType>
z BaseAnimal, musisz go użyć, aby móc rzucać między nimi.źródło
A co z ogólną klasą bazową?
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> { }
EDYCJA : Nowe rozwiązanie, wykorzystujące metody rozszerzające i interfejs znaczników ...
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(); } }
W ten sposób Pies i Kot dziedziczą po Animal (jak zauważono w komentarzach, moje pierwsze rozwiązanie nie zachowało dziedziczenia).
Konieczne jest wyraźne oznaczenie klas za pomocą interfejsu znaczników, co jest bolesne, ale może to może dać ci kilka pomysłów ...
DRUGA EDYCJA @Svish: Zmodyfikowałem kod, aby wyraźnie pokazać, że metoda rozszerzenia nie wymusza w żaden sposób faktu, że
iPooProvider
dziedziczy poBaseAnimal
. Co masz na myśli, mówiąc „jeszcze silniej wpisane na maszynie”?źródło
Nazywa się to kowariancją typu zwracanego i nie jest ogólnie obsługiwane w językach C # ani .NET, pomimo życzeń niektórych osób .
Chciałbym zachować ten sam podpis, ale dodać dodatkową
ENSURE
klauzulę do klasy pochodnej, w której upewniam się, że ta zwraca plikRadioActivePoo
. Krótko mówiąc, wykonałbym przez projekt na podstawie umowy to, czego nie mogę zrobić za pomocą składni.Inni wolą to udawać . Myślę, że to w porządku, ale staram się oszczędzać linie kodu związane z infrastrukturą. Jeśli semantyka kodu jest wystarczająco jasna, cieszę się, a projektowanie na podstawie umowy pozwala mi to osiągnąć, chociaż nie jest to mechanizm czasu kompilacji.
To samo dotyczy leków generycznych, co sugerują inne odpowiedzi . Użyłbym ich z lepszego powodu niż po prostu zwracanie radioaktywnej kupy - ale to tylko ja.
źródło
Istnieje również ta opcja (jawna implementacja interfejsu)
public class Cat:Animal { Poo Animal.Excrement { get { return Excrement; } } public RadioactivePoo Excrement { get { return new RadioactivePoo(); } } }
Tracisz możliwość użycia klasy bazowej do implementacji Cat, ale z drugiej strony zachowujesz polimorfizm między Cat i Dog.
Ale wątpię, że dodatkowa złożoność jest tego warta.
źródło
Dlaczego nie zdefiniować chronionej metody wirtualnej, która tworzy „odchody” i zachowuje właściwość publiczną, która zwraca niewirtualne „odchody”. Następnie klasy pochodne mogą przesłonić zwracany typ klasy bazowej.
W poniższym przykładzie ustawiam „Excrement” jako niewirtualny, ale udostępniam właściwość ExcrementImpl, aby umożliwić klasom pochodnym dostarczanie odpowiedniego „Poo”. Typy pochodne mogą następnie przesłonić zwracany typ „Excrement”, ukrywając implementację klasy bazowej.
Dawny:
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; } } } }
źródło
Popraw mnie, jeśli się mylę, ale nie chodzi o to, aby móc zwrócić RadioActivePoo, jeśli dziedziczy on po Poo, kontrakt byłby taki sam jak klasa abstrakcyjna, ale po prostu zwróciłby RadioActivePoo ()
źródło
Spróbuj tego:
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(); } } } }
źródło
Myślę, że znalazłem sposób, który nie zależy od typów ogólnych lub metod rozszerzających, ale raczej od ukrywania metod. Może jednak złamać polimorfizm, więc zachowaj szczególną ostrożność, jeśli dalej odziedziczysz po Cat.
Mam nadzieję, że ten post nadal może komuś pomóc, pomimo spóźnienia o 8 miesięcy.
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; } } }
źródło
Może pomóc, jeśli RadioactivePoo pochodzi z poo, a następnie użyje leków generycznych.
źródło
FYI. Jest to dość łatwe w Scali.
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)
Oto przykład na żywo, z którym możesz grać: http://www.scalakata.com/50d0d6e7e4b0a825d655e832
źródło
Uważam, że twoja odpowiedź nazywa się kowariancją.
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(); } }
źródło
Możesz po prostu użyć zwrotu Interface. W twoim przypadku IPoo.
W twoim przypadku jest to lepsze niż używanie typu ogólnego, ponieważ używasz klasy bazowej komentarza.
źródło
Poniższe łączy niektóre z najlepszych aspektów kilku innych odpowiedzi, a także technikę umożliwiającą kluczowy aspekt
Cat
posiadaniaExcrement
właściwości wymaganegoRadioactivePoo
typu, ale możliwość zwrócenia jej takPoo
, jakbyśmy tylko wiedzieli, że mamyAnimalBase
raczej niż konkretnie aCat
.Obiekt wywołujący nie musi używać typów ogólnych, mimo że są one obecne w implementacjach, ani wywoływać funkcji o innych nazwach w celu uzyskania specjalnego
Poo
.Klasa pośrednia
AnimalWithSpecialisations
służy jedynie do zapieczętowaniaExcrement
własności, łącząc ją poprzez niepublicznąSpecialPoo
właściwość z klasą pochodną,AnimalWithSpecialPoo<TPoo>
która maExcrement
właściwość pochodnego typu zwracanego.Jeśli
Cat
jest to jedyne zwierzę, którePoo
jest w jakikolwiek sposób wyjątkowe, lub nie chcemy, aby typExcrement
był główną cechą definiującą aCat
, pośrednia klasa ogólna może zostać pominięta w hierarchii, tak żeCat
pochodzi bezpośrednio zAnimalWithSpecialisations
, ale jeśli istnieje istnieje kilka różnych zwierząt, których podstawową cechą jest to, żePoo
są one w jakiś sposób wyjątkowe, rozdzielenie „kotłowni” na klasy pośrednie pomaga utrzymaćCat
samą klasę w dość czystym stanie, aczkolwiek kosztem kilku dodatkowych wywołań funkcji wirtualnych.Przykładowy kod pokazuje, że większość oczekiwanych operacji działa „zgodnie z oczekiwaniami”.
public interface IExcretePoo<out TPoo> where TPoo : Poo { TPoo Excrement { get; } } public class Poo { } public class RadioactivePoo : Poo { } public class AnimalBase : IExcretePoo<Poo> { public virtual Poo Excrement { get { return new Poo(); } } } public class Dog : AnimalBase { // No override, just return normal poo like normal animal } public abstract class AnimalWithSpecialisations : AnimalBase { // this class connects AnimalBase to AnimalWithSpecialPoo<TPoo> public sealed override Poo Excrement { get { return SpecialPoo; } } // if not overridden, our "special" poo turns out just to be normal animal poo... protected virtual Poo SpecialPoo { get { return base.Excrement; } } } public abstract class AnimalWithSpecialPoo<TPoo> : AnimalWithSpecialisations, IExcretePoo<TPoo> where TPoo : Poo { sealed protected override Poo SpecialPoo { get { return Excrement; } } public new abstract TPoo Excrement { get; } } public class Cat : AnimalWithSpecialPoo<RadioactivePoo> { public override RadioactivePoo 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.Excrement; AnimalBase animal = cat; Poo animalPoo = catPoo; animalPoo = animal.Excrement; AnimalWithSpecialPoo<RadioactivePoo> radioactivePooingAnimal = cat; RadioactivePoo radioactivePoo = radioactivePooingAnimal.Excrement; IExcretePoo<Poo> pooExcreter = cat; // through this interface we don't know the Poo was radioactive. IExcretePoo<RadioactivePoo> radioactivePooExcreter = cat; // through this interface we do. // we can replace these with the dog equivalents: animal = dog; animalPoo = dogPoo; pooExcreter = dog; // but we can't do: // radioactivePooExcreter = dog; // radioactivePooingAnimal = dog; // radioactivePoo = dogPoo; }
źródło
C # 9 daje nam kowariantne zastępowanie zwracanych typów. Zasadniczo: to, czego chcesz, po prostu działa .
źródło
Cóż, w rzeczywistości możliwe jest zwrócenie konkretnego Type, który różni się od odziedziczonego zwracanego Type (nawet w przypadku metod statycznych), dzięki
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); } }
Zaimplementowałem to w opakowaniu API, które napisałem dla mojej firmy. Jeśli planujesz opracować interfejs API, może to poprawić użyteczność i doświadczenie deweloperskie w niektórych przypadkach użycia. Niemniej jednak użycie
dynamic
ma wpływ na wydajność, więc staraj się tego unikać.źródło