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?
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?
Risposte:
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
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.
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à.
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.
BaseExtensionqui sia almeno implementato in IBasemodo da poterlo usare in contesti simili? O non era importante per le tue esigenze?
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.
Il tipo di riferimento ha altri tre casi situazionali principali in cui qualsiasi scenario può trovarsi.
Caso 1. Figlio di un genitore diretto o indiretto
Employee e = new Employee();
Person p = (Person)e; //Allowed
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.
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 .
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:
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.
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
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!
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 ...
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.