Impossibile trasmettere dalla classe padre alla classe figlio


97

Sto provando a trasmettere da una classe genitore a una classe figlio ma ottengo un'eccezione InvalidCastException. La classe figlia ha solo una proprietà di tipo int. Qualcuno sa cosa devo fare?


È anche utile sapere che non è possibile utilizzare il casting esplicito per le classi correlate di base / derivazione.
Rzassar

Risposte:


133

Un modo semplice per eseguire il downcast in C # consiste nel serializzare il padre e quindi deserializzarlo nel figlio.

 var serializedParent = JsonConvert.SerializeObject(parentInstance); 
 Child c  = JsonConvert.DeserializeObject<Child>(serializedParent);

Ho una semplice app per console che trasforma l'animale in cane, usando le due righe di codice qui sopra


19
Ebbene, esiterei a definirlo un "abbattuto".
Kirk Woll

Solo una nota, i nomi delle variabili non sono gli stessi sopra.
Jake Gaston

5
Adoro quando qualcuno pensa fuori dagli schemi e zittisce le persone dicendo all'OP che non può essere fatto (tranne uno o due troll)! Grazie per l'assistenza su questo. Ho cercato di capirlo nelle ultime due ore :)
derekmx271

3
Questa è un'ottima soluzione. Ho avuto un caso in cui la mia classe figlio era solo un wrapper per un genitore senza funzionalità aggiuntive. L'ho fatto in modo da non dover importare il riferimento Web nella mia applicazione poiché era nella mia libreria di supporto. Questo mi ha permesso di convertire il genitore nella mia classe wrapper. Grazie!
BrianVPS

1
Sei un genio! :)
Yablargo

118

Non puoi lanciare un mammifero in un cane, potrebbe essere un gatto.

Non puoi gettare un cibo in un panino, potrebbe essere un cheeseburger.

Non puoi lanciare un'auto in una Ferrari - potrebbe essere una Honda, o più specificamente, non puoi lanciare una Ferrari 360 Modena su una Ferrari 360 Challange Stradale - ci sono parti diverse, anche se sono entrambe Ferrari 360.


17
Blocchi stradali comprensibili, da qui l'impossibilità di "lanciare" effettivamente in questo modo. Ma cosa succede se vuole un cane che abbia lo stesso colore degli occhi / peso / modello di capelli / età, ecc. Del gatto che è tenuto nell'oggetto mammifero? Essenzialmente copiando le proprietà comuni.
FastAl

7
FastAl, questo è esattamente il motivo per cui abbiamo interfacce. Il mammifero deve implementare IMammal e contenere il colore degli occhi, il peso, ecc. Ora puoi lanciare sia cane che gatto su IMammal.
Tom Deloford

1
Puoi trasformare un mammifero in un cane. Se è un cane, è un cane. In caso contrario, diventa nullo. Le funzioni di "sovraccarico" possono rendere possibile la conversione impossibile da gatto a cane, se il gatto ha queste funzioni di sovraccarico che lo consentono. Ma è tuo compito gestire la perdita di dati e adattare i dati inesistenti. Come convertire gli artigli in chiodi, inseguire la corda in inseguire la palla, ecc ...
TamusJRoyce

Penso che gli esempi siano un po 'estremi e selettivi e forse il cast è una scorciatoia per un costruttore di copie. Ad esempio, costruire una ferrari con le proprietà definite nell'oggetto di base auto. Oppure, inizia con un essere umano e crea un ragazzo. Casting e utilizzo diretto? D'accordo che è un no-no. Ma se fa parte del costruttore o qualcosa del genere, potrebbe funzionare. La risposta alla serializzazione di seguito è un bel tocco.
sirthomas

1
Analogia Ferrari NIZZA
Lord Darth Vader

57

L'istanza a cui si riferisce il riferimento della classe base non è un'istanza della classe figlia. Non c'è nulla di sbagliato.

Più specificamente:

Base derivedInstance = new Derived();
Base baseInstance = new Base();

Derived good = (Derived)derivedInstance; // OK
Derived fail = (Derived)baseInstance; // Throws InvalidCastException

Affinché il cast abbia successo, l'istanza che stai abbattendo deve essere un'istanza della classe a cui stai abbattendo (o almeno, la classe a cui stai abbattendo deve essere all'interno della gerarchia di classi dell'istanza), altrimenti il il cast fallirà.


o potenzialmente base otherDerived = new OtherDerived (); Derived otherFail = (Derived) otherDerived;
Blair Conrad

class Base {} class Derived: Base {} // In Main Method Base derivataInstance = new Derived (); Base baseInstance = new Base (); Bene derivato = Istanza derivata (derivata); Derived fail = (Derived) baseInstance; Viene compilato senza errori in .NET 3.5. Dov'è il problema che stai dicendo?
pradeeptp

7
@pradeeptp: Ovviamente costruisce. Chi ha parlato di un errore di compilazione?
Greg D

17

Ci sono alcuni casi in cui un cast del genere avrebbe senso.
Nel mio caso, stavo ricevendo una classe BASE sulla rete e avevo bisogno di più funzionalità. Quindi derivarlo per gestirlo dalla mia parte con tutte le campane e fischietti che volevo, e lanciare la classe BASE ricevuta in quella DERIVATA semplicemente non era un'opzione (Genera InvalidCastException of Course)

Una SOLUZIONE pratica e intuitiva era dichiarare una classe Helper EXTENSION che NON ereditava la classe BASE in realtà, ma la INCLUDEva come membro.

public class BaseExtension
{
   Base baseInstance;

   public FakeDerived(Base b)
   {
      baseInstance = b;
   }

   //Helper methods and extensions to Base class added here
}

Se hai un accoppiamento lento e hai solo bisogno di un paio di funzionalità extra per la classe base senza avere VERAMENTE un bisogno assoluto di derivazione, questa potrebbe essere una soluzione rapida e semplice.


Ho ragione nel pensare che probabilmente vuoi che il tuo BaseExtensionqui sia almeno implementato in IBasemodo da poterlo usare in contesti simili? O non era importante per le tue esigenze?
tobriand

alcune volte, tra cui può essere una sostituzione appropriata per l'eredità
Vahid Ghadiri

17

Ho visto la maggior parte delle persone dire che il casting esplicito da genitore a figlio non è possibile, ma in realtà non è vero. Facciamo un inizio rivisto e proviamo a dimostrarlo con esempi.

Come sappiamo in .net tutti i casting hanno due grandi categorie.

  1. Per tipo di valore
  2. Per il tipo di riferimento (nel tuo caso il suo tipo di riferimento)

Il tipo di riferimento ha altri tre casi situazionali principali in cui qualsiasi scenario può trovarsi.

Da figlio a genitore (Cast implicito - Sempre riuscito)

Caso 1. Figlio di un genitore diretto o indiretto

Employee e = new Employee();
Person p = (Person)e; //Allowed

Da genitore a figlio (Cast esplicito - Può avere successo)

Caso 2. Variabile genitore contenente l'oggetto genitore (non consentito)

Person p = new Person();  // p is true Person object
Employee e = (Employee)p; //Runtime err : InvalidCastException <-------- Yours issue

Caso 3. Variabile padre che contiene l'oggetto figlio (sempre riuscito)

Nota: poiché gli oggetti hanno natura polimorfica, è possibile che una variabile di un tipo di classe genitore contenga un tipo figlio.

Person p = new Employee(); // p actually is Employee
Employee e = (Employee)p; // Casting allowed

Conclusione: dopo aver letto soprattutto, spero che ora abbia senso come è possibile la conversione da genitore a figlio (caso 3).

Rispondi alla domanda :

La tua risposta è nel caso 2. Dove puoi vedere tale casting non è consentito da OOP e stai cercando di violare una delle regole di base di OOP, quindi scegli sempre un percorso sicuro.

Inoltre, per evitare tali situazioni eccezionali .net ha raccomandato di utilizzare is / as operatori che ti aiuteranno a prendere decisioni informate e fornire casting sicuri.


13

Ciò violerebbe i principi orientati agli oggetti. Direi che una soluzione elegante qui e altrove nel progetto sta usando un framework di mappatura degli oggetti come AutoMapper per configurare una proiezione.

Ecco una configurazione leggermente più complessa di quella necessaria, ma abbastanza flessibile per la maggior parte dei casi:

public class BaseToChildMappingProfile : Profile
{
    public override string ProfileName
    {
        get { return "BaseToChildMappingProfile"; }
    }

    protected override void Configure()
    {
        Mapper.CreateMap<BaseClass, ChildClassOne>();
        Mapper.CreateMap<BaseClass, ChildClassTwo>();
    }
}


public class AutoMapperConfiguration
{
    public static void Configure()
    {
        Mapper.Initialize(x =>
        {
            x.AddProfile<BaseToChildMappingProfile>();
        });
    }
}

Quando l'applicazione avvia la chiamata AutoMapperConfiguration.Configure()e quindi puoi proiettare in questo modo:

ChildClassOne child = Mapper.Map<BaseClass, ChildClassOne>(baseClass);

Le proprietà vengono mappate per convenzione, quindi se la classe viene ereditata i nomi delle proprietà sono esattamente gli stessi e la mappatura viene configurata automaticamente. È possibile aggiungere ulteriori proprietà modificando la configurazione. Consulta la documentazione .


Usare Automapper per mappare un tipo con una singola proprietà a un'altra (come l'OP descritto) è come usare una mazza per rompere un uovo. Perché non solo rinnovare il tipo derivato e assegnare la sua proprietà da soli (che è 1 riga di codice).
bytedev

9

Paul, non hai chiesto "Posso farlo" - presumo che tu voglia sapere come farlo!

Abbiamo dovuto farlo su un progetto: ci sono molte classi che abbiamo impostato in modo generico solo una volta, quindi inizializziamo le proprietà specifiche delle classi derivate. Uso VB quindi il mio campione è in VB (noogies difficili), ma ho rubato l'esempio VB da questo sito che ha anche una versione C # migliore:

http://www.eggheadcafe.com/tutorials/aspnet/a4264125-fcb0-4757-9d78-ff541dfbcb56/net-reflection--copy-cl.aspx

Codice di esempio:

Imports System
Imports System.Collections.Generic
Imports System.Reflection
Imports System.Text
Imports System.Diagnostics

Module ClassUtils

    Public Sub CopyProperties(ByVal dst As Object, ByVal src As Object)
        Dim srcProperties() As PropertyInfo = src.GetType.GetProperties
        Dim dstType = dst.GetType

        If srcProperties Is Nothing Or dstType.GetProperties Is Nothing Then
            Return
        End If

        For Each srcProperty As PropertyInfo In srcProperties
            Dim dstProperty As PropertyInfo = dstType.GetProperty(srcProperty.Name)

            If dstProperty IsNot Nothing Then
                If dstProperty.PropertyType.IsAssignableFrom(srcProperty.PropertyType) = True Then
                    dstProperty.SetValue(dst, srcProperty.GetValue(src, Nothing), Nothing)
                End If
            End If
        Next
    End Sub
End Module


Module Module1
    Class base_class
        Dim _bval As Integer
        Public Property bval() As Integer
            Get
                Return _bval
            End Get
            Set(ByVal value As Integer)
                _bval = value
            End Set
        End Property
    End Class
    Class derived_class
        Inherits base_class
        Public _dval As Integer
        Public Property dval() As Integer
            Get
                Return _dval
            End Get
            Set(ByVal value As Integer)
                _dval = value
            End Set
        End Property
    End Class
    Sub Main()
        ' NARROWING CONVERSION TEST
        Dim b As New base_class
        b.bval = 10
        Dim d As derived_class
        'd = CType(b, derived_class) ' invalidcast exception 
        'd = DirectCast(b, derived_class) ' invalidcast exception
        'd = TryCast(b, derived_class) ' returns 'nothing' for c
        d = New derived_class
        CopyProperties(d, b)
        d.dval = 20
        Console.WriteLine(b.bval)
        Console.WriteLine(d.bval)
        Console.WriteLine(d.dval)
        Console.ReadLine()
    End Sub
End Module

Ovviamente questo non è davvero un casting. Sta creando un nuovo oggetto derivato e copiando le proprietà dal genitore, lasciando vuote le proprietà figlio. Questo è tutto ciò di cui avevo bisogno e sembra che sia tutto ciò che devi fare. Nota che copia solo le proprietà, non i membri (variabili pubbliche) nella classe (ma potresti estenderlo per farlo se hai vergogna di esporre membri pubblici).

Il casting in generale crea 2 variabili che puntano allo stesso oggetto (mini tutorial qui, per favore non lanciarmi eccezioni di casi d'angolo). Ci sono conseguenze significative in questo (esercizio per il lettore)!

Ovviamente devo dire perché la languague non ti lascia andare dalla base all'istanza derivata, ma fa il contrario. immagina un caso in cui puoi prendere un'istanza di una casella di testo winforms (derivata) e memorizzarla in una variabile di tipo Winforms control. Ovviamente il "controllo" può spostare l'oggetto su OK e tu puoi gestire tutte le cose "controll-y" sulla casella di testo (ad esempio, proprietà in alto, a sinistra, .text). La roba specifica della casella di testo (ad esempio, .multiline) non può essere vista senza lanciare la variabile di tipo 'control' che punta alla casella di testo in memoria, ma è ancora lì in memoria.

Ora immagina di avere un controllo e di voler inserire una variabile di tipo textbox. Il controllo in memoria manca "multiline" e altre cose textboxy. Se provi a farvi riferimento, il controllo non aumenterà magicamente una proprietà multilinea! La proprietà (guardala come una variabile membro qui, che in realtà memorizza un valore - perché è attiva nella memoria dell'istanza della casella di testo) deve esistere. Dato che stai lanciando, ricorda, deve essere lo stesso oggetto a cui stai puntando. Quindi non è una restrizione linguistica, è filosoficamente impossibile formulare in questo modo.


1
So che è molto dopo, ma dovresti includere "AndAlso dstProperty.CanWrite" nel tuo test "If dstProperty IsNot Nothing", per assicurarti che non sia una proprietà di sola lettura.
JamesMLV

@ JamesMLV - grazie buona cattura. "dopo il fatto" - non sembra che l'OP accetterà comunque alcuna risposta :-( quindi non ci sono fatti da
cercare

4

L'istanza dell'oggetto deve essere creata utilizzando il tipo della classe figlia, non è possibile eseguire il cast di un'istanza di tipo genitore su un tipo figlio


2

Per quanto mi riguarda è stato sufficiente copiare tutti i campi delle proprietà dalla classe base alla genitrice in questo modo:

using System.Reflection;

public static ChildClass Clone(BaseClass b)
{
    ChildClass p = new ChildClass(...);

    // Getting properties of base class

    PropertyInfo[] properties = typeof(BaseClass).GetProperties();

    // Copy all properties to parent class

    foreach (PropertyInfo pi in properties)
    {
        if (pi.CanWrite)
            pi.SetValue(p, pi.GetValue(b, null), null);
    }

    return p;
}

Una soluzione universale per qualsiasi oggetto può essere trovata qui


2

A partire da C # 7.0, è possibile utilizzare la parola chiave is per eseguire questa operazione:

Con quelle classi definite:

class Base { /* Define base class */ }
class Derived : Base { /* Define derived class */ }

Puoi quindi fare qualcosa come:

void Funtion(Base b)
{
    if (b is Derived d)
    {
        /* Do something with d which is now a variable of type Derived */
    }
}

Che sarebbe equivalente a:

void Funtion(Base b)
{
    Defined d;
    if (b is Derived)
    {
        d = (Defined)b;
        /* Do something with d */
    }
}

Ora puoi chiamare:

Function(new Derived()); // Will execute code defined in if

Così come

Function(new Base()); // Won't execute code defined in if

In questo modo puoi essere certo che il tuo abbattimento sarà valido e non farà eccezione!


1

Per eseguire il cast, l' oggetto effettivo deve essere di un Tipo uguale o derivato dal Tipo a cui stai tentando di eseguire il cast ...

oppure, per affermarlo in modo opposto, il tipo a cui si sta tentando di eseguirne il cast deve essere lo stesso o una classe base del tipo effettivo dell'oggetto.

se il tuo oggetto effettivo è di tipo Baseclass , non puoi eseguirne il cast su una classe derivata Type ...


1

Una variazione dell'approccio di serializzazione per coloro che utilizzano ServiceStack:

var child = baseObject.ConvertTo<ChildType>();

o il più prolisso:

var child = baseObject.ToJson().FromJson<ChildType>();

La serializzazione di ServiceStack potrebbe essere super veloce e tutto, ma chiaramente questa non è una soluzione per conversioni massicce in trasferimenti a bassa latenza, né per tipi altamente complessi. Probabilmente è ovvio per chiunque utilizzi ServiceStack, ma ho pensato di chiarire in previsione dei commenti.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.