J'essaye de passer d'une classe parent à une classe enfant mais j'obtiens une InvalidCastException. La classe enfant n'a qu'une seule propriété de type int. Quelqu'un sait-il ce que je dois faire?
J'essaye de passer d'une classe parent à une classe enfant mais j'obtiens une InvalidCastException. La classe enfant n'a qu'une seule propriété de type int. Quelqu'un sait-il ce que je dois faire?
Réponses:
Un moyen simple de downcast en C # consiste à sérialiser le parent, puis à le désérialiser dans l'enfant.
var serializedParent = JsonConvert.SerializeObject(parentInstance);
Child c = JsonConvert.DeserializeObject<Child>(serializedParent);
J'ai une application console simple qui transforme l'animal en chien, en utilisant les deux lignes de code ci-dessus ici
Vous ne pouvez pas jeter un mammifère sur un chien - ce pourrait être un chat.
Vous ne pouvez pas couler un aliment dans un sandwich - cela pourrait être un cheeseburger.
Vous ne pouvez pas jeter une voiture dans une Ferrari - cela peut être une Honda, ou plus précisément, vous ne pouvez pas convertir une Ferrari 360 Modena sur une Ferrari 360 Challange Stradale - il y a des pièces différentes, même si ce sont toutes les deux des Ferrari 360.
L'instance à laquelle votre référence de classe de base fait référence n'est pas une instance de votre classe enfant. Il n'y a rien de mal.
Plus précisement:
Base derivedInstance = new Derived();
Base baseInstance = new Base();
Derived good = (Derived)derivedInstance; // OK
Derived fail = (Derived)baseInstance; // Throws InvalidCastException
Pour que le cast réussisse, l'instance que vous downcasting doit être une instance de la classe vers laquelle vous downcasting (ou du moins, la classe vers laquelle vous downcasting doit être dans la hiérarchie des classes de l'instance), sinon le le casting échouera.
Il y a des cas où une telle distribution aurait du sens.
Dans mon cas, je recevais une classe BASE sur le réseau et j'avais besoin de plus de fonctionnalités. Donc, le dériver pour le gérer de mon côté avec toutes les cloches et les sifflets que je voulais, et lancer la classe BASE reçue dans celle DERIVED n'était tout simplement pas une option (Throws InvalidCastException of Course)
Une solution pratique et prête à l' emploi était de déclarer une classe EXTENSION Helper qui n'héritait PAS de la classe BASE en fait, mais l'INCLUT en tant que membre.
public class BaseExtension
{
Base baseInstance;
public FakeDerived(Base b)
{
baseInstance = b;
}
//Helper methods and extensions to Base class added here
}
Si vous avez un couplage lâche et avez juste besoin de quelques fonctionnalités supplémentaires pour la classe de base sans VRAIMENT avoir un besoin absolu de dérivation, cela pourrait être une solution de contournement rapide et simple.
BaseExtensionici soit au moins mis en œuvre de manière IBaseà pouvoir l'utiliser dans des contextes similaires? Ou n'était-ce pas important pour vos besoins?
J'ai vu la plupart des gens dire que le casting explicite parent-enfant n'est pas possible, ce n'est en fait pas vrai. Prenons un départ révisé et essayons de le prouver par des exemples.
Comme nous le savons dans .net, tous les castings ont deux grandes catégories.
Le type de référence a trois autres cas de situation principaux dans lesquels n'importe quel scénario peut se trouver.
Cas 1. Enfant à un parent direct ou indirect
Employee e = new Employee();
Person p = (Person)e; //Allowed
Cas 2. Variable parent contenant un objet parent (non autorisé)
Person p = new Person(); // p is true Person object
Employee e = (Employee)p; //Runtime err : InvalidCastException <-------- Yours issue
Cas 3. Variable parent contenant un objet enfant (toujours réussi)
Remarque: les objets étant de nature polymorphe, il est possible qu'une variable d'un type de classe parent contienne un type enfant.
Person p = new Employee(); // p actually is Employee
Employee e = (Employee)p; // Casting allowed
Conclusion: Après avoir lu avant tout, j'espère que cela aura du sens maintenant, comme comment la conversion parent-enfant est possible (cas 3).
Réponse à la question:
Votre réponse est dans le cas 2. Là où vous pouvez voir une telle diffusion n'est pas autorisée par la POO et que vous essayez de violer l'une des règles de base de la POO, alors choisissez toujours un chemin sûr.
De plus, pour éviter de telles situations exceptionnelles, .net a recommandé d'utiliser is / en tant qu'opérateurs, ceux-ci vous aideront à prendre des décisions éclairées et à assurer une diffusion en toute sécurité.
Cela violerait les principes orientés objet. Je dirais qu'une solution élégante ici et ailleurs dans le projet utilise un cadre de mappage d'objets comme AutoMapper pour configurer une projection.
Voici une configuration légèrement plus complexe que nécessaire mais suffisamment flexible pour la plupart des cas:
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>();
});
}
}
Lorsque l'application démarre, appelez AutoMapperConfiguration.Configure()et vous pouvez projeter comme ceci:
ChildClassOne child = Mapper.Map<BaseClass, ChildClassOne>(baseClass);
Les propriétés sont mappées par convention, donc si la classe est héritée, les noms de propriété sont exactement les mêmes et le mappage est configuré automatiquement. Vous pouvez ajouter des propriétés supplémentaires en peaufinant la configuration. Consultez la documentation .
Paul, tu n'as pas demandé 'Puis-je le faire' - je suppose que tu veux savoir comment le faire!
Nous devions le faire sur un projet - il y a beaucoup de classes que nous avons configurées de manière générique une seule fois, puis initialisons les propriétés spécifiques aux classes dérivées. J'utilise VB donc mon échantillon est en VB (noogies difficiles), mais j'ai volé l'échantillon VB de ce site qui a également une meilleure version C #:
Exemple de code:
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
Bien sûr, ce n'est pas vraiment du casting. Il crée un nouvel objet dérivé et copie les propriétés du parent, laissant les propriétés enfants vides. C'est tout ce que j'avais à faire et on dirait que c'est tout ce que vous devez faire. Notez qu'il ne copie que les propriétés, pas les membres (variables publiques) dans la classe (mais vous pouvez l'étendre pour le faire si vous êtes par honte à exposer des membres publics).
La diffusion en général crée 2 variables pointant vers le même objet (mini tutoriel ici, veuillez ne pas me lancer d'exceptions de casse de coin). Il y a des ramifications importantes à cela (exercice pour le lecteur)!
Bien sûr, je dois dire pourquoi la languague ne vous laisse pas passer de la base à l'instance dérivée, mais fait l'inverse. Imaginez un cas où vous pouvez prendre une instance d'une zone de texte winforms (dérivée) et la stocker dans une variable de type contrôle Winforms. Bien sûr, le 'contrôle' peut déplacer l'objet autour de OK et vous pouvez gérer toutes les choses 'controll-y' concernant la zone de texte (par exemple, en haut, à gauche, les propriétés .text). Les éléments spécifiques à la zone de texte (par exemple, .multiline) ne peuvent pas être vus sans lancer la variable de type «contrôle» pointant vers la zone de texte en mémoire, mais elle est toujours là en mémoire.
Imaginez maintenant que vous avez un contrôle et que vous souhaitez y placer une variable de type zone de texte. Le contrôle en mémoire manque «multiligne» et d'autres éléments textboxy. Si vous essayez de les référencer, le contrôle ne développera pas comme par magie une propriété multiligne! La propriété (regardez-la comme une variable membre ici, qui stocke en fait une valeur - car il y en a dans la mémoire de l'instance de la zone de texte) doit exister. Puisque vous lancez, rappelez-vous que ce doit être le même objet que vous pointez. Il ne s'agit donc pas d'une restriction linguistique, il est philosophiquement impossible de caser de cette manière.
Quant à moi, il suffisait de copier tous les champs de propriété de la classe de base vers le parent comme ceci:
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;
}
Une solution universelle pour tout objet peut être trouvée ici
À partir de C # 7.0, vous pouvez utiliser le mot clé is pour ce faire:
Avec ces classes définies:
class Base { /* Define base class */ }
class Derived : Base { /* Define derived class */ }
Vous pouvez alors faire quelque chose comme:
void Funtion(Base b)
{
if (b is Derived d)
{
/* Do something with d which is now a variable of type Derived */
}
}
Ce qui équivaudrait à:
void Funtion(Base b)
{
Defined d;
if (b is Derived)
{
d = (Defined)b;
/* Do something with d */
}
}
Vous pouvez maintenant appeler:
Function(new Derived()); // Will execute code defined in if
Aussi bien que
Function(new Base()); // Won't execute code defined in if
De cette façon, vous pouvez être sûr que votre downcast sera valide et ne lèvera pas d'exception!
Pour effectuer un cast, l' objet réel doit être d'un Type égal ou dérivé du Type vers lequel vous essayez de convertir ...
ou, pour le déclarer de manière opposée, le Type vers lequel vous essayez de le convertir doit être le même que, ou une classe de base, du type réel de l'objet.
si votre objet réel est de type Baseclass , vous ne pouvez pas le convertir en une classe dérivée Type ...
Une variante de l'approche de sérialisation pour ceux qui utilisent ServiceStack:
var child = baseObject.ConvertTo<ChildType>();
ou le plus verbeux:
var child = baseObject.ToJson().FromJson<ChildType>();
La sérialisation de ServiceStack est peut-être super rapide et tout, mais clairement, ce n'est pas une solution pour les conversions massives dans les transferts à faible latence, ni pour les types très complexes. C'est probablement évident pour quiconque utilise ServiceStack, mais j'ai pensé que je clarifierais en prévision des commentaires.