Domanda

Esiste un modo per sovrascrivere i tipi restituiti in C#?Se sì, come e, in caso contrario, perché e qual è il metodo consigliato per farlo?

Il mio caso è che ho un'interfaccia con una classe base astratta e i suoi discendenti.Vorrei fare questo (ok, non proprio, ma come esempio!):

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 ovviamente eredita da Poo.

La mia ragione per volerlo è che coloro che lo usano Cat gli oggetti potrebbero utilizzare il file Excrement proprietà senza dover lanciare il Poo in RadioactivePoo mentre ad esempio il Cat potrebbe ancora far parte di un Animal elenco in cui gli utenti potrebbero non essere necessariamente consapevoli o preoccuparsi delle loro feci radioattive.Spero che avesse senso...

Per quanto posso vedere, il compilatore non lo consente almeno.Quindi immagino sia impossibile.Ma cosa consiglieresti come soluzione a questo?

È stato utile?

Soluzione

Che dire di una classe base generica?

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

MODIFICA : una nuova soluzione che utilizza metodi di estensione e un'interfaccia marcatore ...

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

In questo modo Dog e Cat ereditano entrambi da Animal (come osservato nei commenti, la mia prima soluzione non ha preservato l'eredità).
È necessario contrassegnare esplicitamente le classi con l'interfaccia marker, il che è doloroso, ma forse questo potrebbe darti alcune idee ...

SECONDA MODIFICA @Svish: ho modificato il codice per mostrare esplicitamente che il metodo di estensione non sta applicando in alcun modo il fatto che iPooProvider erediti da BaseAnimal. Cosa intendi con & Quot; ancora più tipicamente & Quot ;?

Altri suggerimenti

Questo è chiamato covarianza del tipo di ritorno e non è supportato in C # o .NET in generale, nonostante alcune persone desideri .

Quello che vorrei fare è mantenere la stessa firma ma aggiungere un'ulteriore ENSURE clausola alla classe derivata in cui mi assicuro che questa restituisca un RadioActivePoo. Quindi, in breve, farei tramite la progettazione per contratto ciò che non posso fare tramite la sintassi.

Altri preferiscono invece fake . Va bene, immagino, ma tendo a risparmiare & Quot; infrastruttura & Quot; righe di codice. Se la semantica del codice è abbastanza chiara, sono contento e la progettazione per contratto mi permette di raggiungere questo obiettivo, sebbene non sia un meccanismo di compilazione.

Lo stesso per generici, che altre risposte suggeriscono. Li userei per un motivo migliore rispetto al semplice ritorno di cacca radioattiva - ma sono solo io.

So che ci sono già molte soluzioni per questo problema, ma penso di averne escogitato uno che risolve i problemi che ho avuto con le soluzioni esistenti.

Non ero soddisfatto di alcune delle soluzioni esistenti per i seguenti motivi:

  • La prima soluzione di Paolo Tedesco: Cat and Dog non ha una classe base comune.
  • La seconda soluzione di Paolo Tedesco: è un po 'complicata e difficile da leggere.
  • La soluzione di Daniel Daranas: funziona, ma ingombrerebbe il tuo codice con un sacco di inutili dichiarazioni di casting e Debug.Assert ().
  • Le soluzioni di hjb417: questa soluzione non ti consente di mantenere la tua logica in una classe base. La logica è piuttosto banale in questo esempio (chiamare un costruttore) ma in un esempio reale non lo sarebbe.

La mia soluzione

Questa soluzione dovrebbe superare tutti i problemi che ho menzionato sopra usando sia la generica sia il metodo nascosto.

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

Con questa soluzione non è necessario sovrascrivere nulla in Dog OR Cat! Ecco alcuni esempi di utilizzo:

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

Questo produrrà: " RadioactivePoo " due volte che dimostra che il polimorfismo non è stato rotto.

Ulteriori letture

  • Implementazione esplicita dell'interfaccia
  • new Modifier . Non l'ho usato in questa soluzione semplificata, ma potresti averne bisogno in una soluzione più complicata. Ad esempio, se si desidera creare un'interfaccia per BaseAnimal, è necessario utilizzarla nella declerazione di & Quot; PooType Excrement & Quot ;.
  • out Generic Modifier (Covariance) . Ancora una volta non l'ho usato in questa soluzione ma se volevi fare qualcosa come return MyType<Poo> da IAnimal e return MyType<PooType> da BaseAnimal, allora dovresti usarlo per poter eseguire il cast tra i due.

Esiste anche questa opzione (implementazione esplicita dell'interfaccia)

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

Perdi la possibilità di usare la classe base per implementare Cat, ma sul lato positivo, mantieni il polimorfismo tra Cat e Dog.

Ma dubito che ne valga la pena aggiungere la complessità.

Perché non definire un metodo virtuale protetto che crei "Excrement" e mantenere la proprietà pubblica che restituisce "Excrement" non virtuale.Quindi le classi derivate possono sovrascrivere il tipo restituito della classe base.

Nell'esempio seguente, rendo 'Excrement' non virtuale ma fornisco la proprietà ExcrementImpl per consentire alle classi derivate di fornire il corretto 'Poo'.I tipi derivati ​​possono quindi sovrascrivere il tipo restituito di "Excrement" nascondendo l'implementazione della classe base.

Ex.:

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

Correggimi se sbaglio ma non è tutto il punto del polimorfismo per poter restituire RadioActivePoo se eredita da Poo, il contratto sarebbe lo stesso della classe astratta ma restituirebbe semplicemente RadioActivePoo ()

Prova questo:

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

Penso di aver trovato un modo che non dipende da generici o metodi di estensione, ma piuttosto da metodi nascosti. Può rompere il polimorfismo, tuttavia, quindi fai particolare attenzione se erediti ulteriormente da Cat.

Spero che questo post possa ancora aiutare qualcuno, nonostante sia in ritardo di 8 mesi.

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

Potrebbe essere utile se RadioactivePoo è derivato dalla cacca e quindi usa generici.

FYI. Questo è implementato abbastanza facilmente in 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)

Ecco un esempio dal vivo con cui puoi giocare: http://www.scalakata.com/50d0d6e7e4b0a825d655e832

Credo che la tua risposta si chiami covarianza.

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

Potresti semplicemente usare un ritorno a un'interfaccia. Nel tuo caso, IPoo.

Questo è preferibile all'utilizzo di un tipo generico, nel tuo caso, perché stai usando una classe base di commenti.

Bene, in realtà è possibile restituire un Tipo concreto che varia dal Tipo restituito ereditato (anche per metodi statici), grazie a 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);
    }
}

L'ho implementato in un wrapper API che ho scritto per la mia azienda. Se si prevede di sviluppare un'API, ciò ha il potenziale per migliorare l'usabilità e l'esperienza di sviluppo in alcuni casi d'uso. Tuttavia, l'utilizzo di <=> ha un impatto sulle prestazioni, quindi cerca di evitarlo.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top