Come restringere il parametro del metodo alla classe, dove il metodo viene sovrascritto

0

Ho una gerarchia di classi abbastanza semplice:

public class Base
{
    //...
    public virtual void AssignFrom(Base baseObj)
    {
        //DoSomeStuff
    }
}
public class DerivedA : Base
{
    //...

    public override void AssignFrom(Base derivedObj)
    //Wish Method would be:
    //public override void AssignFrom(DerivedA derivedObj)
    {
        base.AssignFrom(derivedObj);
        var objAsDerivedA = derivedObj as DerivedA;
        //Some other stuff concerning derived class
    }
}
public class DerivedB : Base
    {
    //...

    public override void AssignFrom(Base derivedObj)
    //Wish Method would be:
    //public override void AssignFrom(DerivedB derivedObj)
    {
        base.AssignFrom(derivedObj);
        //Here I have to cast, what I would gladly avoid.
        var objAsDerivedB = derivedObj as DerivedB;
        //Some other stuff concerning derived class
    }
}

Ciò che mi infastidisce è che ogni fratello della classe derivata o della sua discendente può essere usato come parametro.

Sarebbe possibile creare un proprio metodo non virtuale per ogni classe, ma a mio parere l'associazione / connessione tra i metodi andrà persa.

Domanda: esiste un modo per limitare il parametro del metodo alla classe, in cui il metodo viene sovrascritto.

    
posta Rekshino 19.07.2018 - 16:37
fonte

6 risposte

2

Quindi puoi fare un solo livello di ereditarietà con Generics. Ma un secondo livello fallisce essenzialmente perché si sta tentando di rimuovere il metodo pubblico AssignFrom della classe base

public class Base
{
    public string MyProp { get; set; }
}

public class Base<T> : Base where T : Base
{
    //...
    public virtual void AssignFrom(T baseObj)
    {
        this.MyProp = baseObj.MyProp;
    }
}


public class DerivedA : Base<DerivedA>
{
    public string ExtraProp { get; set; }
    //...
    public override void AssignFrom(DerivedA derivedObj)
    {
        base.AssignFrom(baseObj);
        this.ExtraProp = baseObj.ExtraProp;
    }
}

public class DerivedB : Base<DerivedB>
{
    public override void AssignFrom(DerivedB derivedObj)
    {
        //this is fine
    }
}

public class DerivedB : DerivedA
{
    public override void AssignFrom(DerivedB derivedObj)
    {
        //this wont compile: no suitable method to override
    }
}

public class DerivedB : DerivedA, Base<DerivedB> //multiple inheritance not allowed!
{
    public override void AssignFrom(DerivedB derivedObj)
    {
    }
}

Un'interfaccia generica può imporre l'aggiunta di un metodo AssignFrom (DerivedB baseObj) ma non la rimozione dell'oggetto AssignFrom (DerivedA baseObj)

dell'oggetto di base

In sostanza si dovrebbe semplicemente aggiungere metodi per classe.

Dovrai superare la tua totale dipendenza da Base.

La soluzione che ritengo sia Copy Contstructors

public class Base
{
    public Base(Base baseObj) { }
}

public class DerivedA : Base
{
    public DerivedA(DerivedA derivedObj) : base(derivedObj) { }
}

public class DerivedB : DerivedA
{
    public DerivedB(DerivedB derivedObj) : base(derivedObj){ }
}

Ora puoi implementare un costruttore che invia qualcosa alla classe base, ma i costruttori non sono ereditati. Quindi non devi rimuovere il costruttore delle classi base dalla tua classe derivata

    
risposta data 19.07.2018 - 17:13
fonte
1

Specificamente correlato a C #, la risposta è no, per il prossimo futuro.

Quello che stai chiedendo sono chiamati tipi di ritorno covarianti. Esiste una proposta "campione" sul repository di linguaggio csharplang per questa funzione . Ciò significa che il team linguistico C # è pronto a discutere almeno l'idea. Ma non è ancora stato programmato alcun rilascio imminente della lingua, supponendo che accada mai.

    
risposta data 19.07.2018 - 17:05
fonte
1

Penso che ti aiuterà.

È possibile utilizzare il tipo generico (T) per garantire il tipo di classi nidificate. Nelle classi annidate, includi il tipo di esso.

public class Base<T>
{
    public T BaseObj;
    //...
    public virtual void AssignFrom(T baseObj)
    {
        //DoSomeStuff
        BaseObj = baseObj;
    }
}
public class DerivedA : Base<DerivedA>
{
    //...

    public override void AssignFrom(DerivedA derivedObj)
    {
        base.AssignFrom(derivedObj);
        var objAsDerivedA = derivedObj;
        //Some other stuff concerning derived class
    }
}
public class DerivedB : Base<DerivedB>
{
    //...

    public override void AssignFrom(DerivedB derivedObj)
    { 
        base.AssignFrom(derivedObj);
        var objAsDerivedB = derivedObj;
        //Some other stuff concerning derived class
    }
}

Spero sia stato utile per te. Buon codice!

    
risposta data 19.07.2018 - 17:13
fonte
0

In primo luogo, puoi farlo con test dinamici: controlla il tipo dell'argomento in DerivedA :: AssignFrom () e lancia se non è del tipo giusto (o asserisci).

Ma in un linguaggio come C #, questo non ha senso farlo attraverso l'analisi statica.

Considera questo codice:

public Base SomeFactoryFunction()
{ 
     return random(0,1)%2=1? new DerviedA () : new DerviedB ();
}

...
public void some_other_code()
{
    Base b = new Base ();
    DerivedA da = new DerivedA ();
    DerivedB db = new DerivedB ();

    // should this compile? - Maybe no
    da.AssignFrom (SomeFactoryFunction());

    b = da;
    // should this compile?
    b.AssignFrom (SomeFactoryFunction());
 }

La chiave è - che in un linguaggio come C #, praticamente non conosci mai i tipi dinamici degli oggetti sottostanti (staticamente).

Il motivo per cui funziona in C ++ (ci sono casi in cui non c'è nemmeno) - ma in C ++ - puoi spesso strutturare il tuo codice usando le classi così in fase di compilazione (staticamente) - il compilatore conosce i tipi dinamici del oggetti, e così può eseguire questo tipo di controllo (più o meno lo stesso codice sopra che non funziona in C # funziona in C ++ se si perde la parte 'nuova' - solo usando oggetti basati sullo stack).

    
risposta data 19.07.2018 - 17:03
fonte
0

Sfortunatamente non è possibile ottenere questo tipo di sicurezza al 100% in C♯. Avresti bisogno di una caratteristica tipicamente chiamata "MyType" per quello, che C♯ non ha. Un MyType è essenzialmente un'annotazione di tipo che dice "tipo di questo oggetto". Pertanto, per un'istanza di DerivedA , MyType sarebbe DerivedA e così via. Puoi immaginare qualcosa del genere:

public class Base
{
    //...
    public virtual void AssignFrom(typeof(this) baseObj)
    {
        //DoSomeStuff
    }
}

Scala ha un this.type , ma non sarebbe di aiuto neanche a te, dal momento che è un tipo singleton la cui unica istanza è this . L'unico oggetto a cui sarebbe permesso passare in quel metodo, sarebbe this che è ovviamente stupido, dato che sei già che passa this come argomento invisibile di zeroth comunque. (È utile per i tipi di ritorno, però, dove ti dice che il metodo restituisce il suo ricevitore.) Sarebbe simile a questo in Scala:

class Base {
  //...
  public assignFrom(baseObj: this.type) = {
    //DoSomeStuff
  }
}

La cosa più vicina che puoi ottenere è usare Polymorphism F-bound , dove passi la classe extender come argomento tipo:

public class Base<T> where T: Base<T>
{
    //...
    public virtual void AssignFrom(T baseObj)
    {
        //DoSomeStuff
    }
}
public class DerivedA : Base<DerivedA>
{
    //...

    public override void AssignFrom(DerivedA derivedObj)
    {
        base.AssignFrom(derivedObj);
        //Some other stuff concerning derived class
    }
}
public class DerivedB : Base<DerivedB>
    {
    //...

    public override void AssignFrom(DerivedB derivedObj)
    {
        base.AssignFrom(derivedObj);
        //Some other stuff concerning derived class
    }
}

Si noti, tuttavia, che questo non è sicuro dal punto di vista del tipo "corretto" MyType, dal momento che nessuno impedisce a un client di fare qualcosa del genere:

public class Nonsense : Base<SomeTotallyUnrelatedClass>
    {
    //...

    public override void AssignFrom(SomeTotallyUnrelatedClass derivedObj)
    {
        base.AssignFrom(derivedObj);
        //Some other stuff concerning derived class
    }
}
    
risposta data 19.07.2018 - 17:17
fonte
0

Puoi semplicemente introdurre i metodi desiderati, piuttosto che sovrascrivere. Nel giusto contesto, il compilatore invocherà il metodo appropriato. (Questo comunque non impedirà assolutamente di usare Base.AssignFrom , comunque.)

public class Base
{
    //...
    public virtual void AssignFrom(Base baseObj)
    {
        //DoSomeStuff
    }
}
public class DerivedA : Base
{
    //...

    public void AssignFrom(DerivedA derivedObj)
    {
        base.AssignFrom(derivedObj);
        var objAsDerivedA = derivedObj as DerivedA;
        //Some other stuff concerning derived class
    }
}
public class DerivedB : Base
    {
    //...

    public void AssignFrom(DerivedB derivedObj)
    {
        base.AssignFrom(derivedObj);
        //Here I have to cast, what I would gladly avoid.
        var objAsDerivedB = derivedObj as DerivedB;
        //Some other stuff concerning derived class
    }
}

FYI, puoi anche fare entrambe le cose: l'override e il metodo introdotto di recente:

public override void AssignFrom(Base obj) { }
public void AssignFrom(DerivedA derivedObj) { }
    
risposta data 19.07.2018 - 17:18
fonte

Leggi altre domande sui tag