Impossible de diffuser de la classe parent à la classe enfant


97

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?


Il est également bon de savoir que vous ne pouvez pas utiliser de conversion explicite pour les classes liées de base / dérivée.
Rzassar

Réponses:


133

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


19
Eh bien, j'hésiterais à appeler cela un "abattu".
Kirk Woll

Juste une note, les noms des variables ne sont pas les mêmes ci-dessus.
Jake Gaston

5
J'adore quand quelqu'un sort des sentiers battus et fait taire les gens en disant à l'OP que cela ne peut pas être fait (sauf pour un ou deux trolls)! Merci pour l'aide sur celui-ci. J'essaie de comprendre cela depuis quelques heures :)
derekmx271

3
C'est une excellente solution. J'ai eu un cas où ma classe enfant était juste un wrapper pour un parent sans fonctionnalité supplémentaire. Je l'ai fait pour ne pas avoir à importer la référence Web dans mon application car elle se trouvait dans ma bibliothèque d'aide. Cela m'a permis de convertir le parent dans ma classe wrapper. Je vous remercie!
BrianVPS

1
Tu es un génie! :)
Yablargo

118

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.


17
Des barrages routiers compréhensibles, d'où l'impossibilité de «lancer» réellement de cette manière. Mais que se passe-t-il s'il veut un chien qui a la même couleur / poids / modèle de cheveux / âge, etc. que le chat qui est tenu dans l'objet mammifère? Copie essentiellement les propriétés communes.
FastAl

7
FastAl, c'est exactement pourquoi nous avons des interfaces. Les mammifères doivent mettre en œuvre IMammal et contenir la couleur des yeux, le poids, etc. Vous pouvez maintenant lancer le chien et le chat sur IMammal.
Tom Deloford

1
Vous pouvez essayer de convertir un mammifère en chien. Si c'est un chien, c'est un chien. Sinon, il devient nul. Les fonctions de "surcharge" peuvent rendre possible la conversion impossible de chat en chien, si cat a ces fonctions surchargées le permettant. Mais c'est votre travail de gérer la perte de données et d'adapter les données non existantes. Comme convertir des griffes en clous, chasser la ficelle en chassant la balle, etc ...
TamusJRoyce

Je pense que les exemples sont un peu extrêmes et sélectifs et peut-être que la distribution est un raccourci pour un constructeur de copie. Par exemple, construire une ferrari avec les propriétés définies dans l'objet de base car. Ou, commencez par un humain et créez un garçon. Casting et utilisation directe? D'accord, c'est un non-non. Mais si cela fait partie du constructeur ou quelque chose du genre, cela pourrait fonctionner. La réponse de sérialisation ci-dessous est une bonne idée.
sirthomas

1
Analogie Ferrari NICE
Lord Darth Vader

57

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.


ou potentiellement Base otherDerived = new OtherDerived (); Derived otherFail = (Derived) otherDerived;
Blair Conrad

class Base {} class Derived: Base {} // In Main Method Base derivedInstance = new Derived (); Base baseInstance = new Base (); Dérivé bon = (dérivé) dérivéInstance; Échec dérivé = (dérivé) baseInstance; Cela compile sans aucune erreur dans .NET 3.5. Où est le problème que vous dites?
pradeeptp

7
@pradeeptp: Bien sûr, il construit. Qui a dit quoi que ce soit à propos d'une erreur de compilation?
Greg D

17

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.


Ai-je raison de penser que vous voulez probablement que votre 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?
tobriand

parfois, y compris peut être une substitution appropriée pour l'héritage
Vahid Ghadiri

17

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.

  1. Pour le type de valeur
  2. Pour le type de référence (dans votre cas, son type de référence)

Le type de référence a trois autres cas de situation principaux dans lesquels n'importe quel scénario peut se trouver.

Enfant à parent (casting implicite - toujours réussi)

Cas 1. Enfant à un parent direct ou indirect

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

Parent à enfant (casting explicite - peut réussir)

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é.


13

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 .


Utiliser Automapper pour mapper un type avec une seule propriété à une autre (comme l'OP décrit) revient à utiliser une masse pour casser un œuf. Pourquoi ne pas simplement renouveler le type dérivé et attribuer vous-même sa propriété (qui correspond à 1 ligne de code).
bytedev

9

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 #:

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

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.


1
Je sais que c'est bien après coup, mais vous devriez inclure "AndAlso dstProperty.CanWrite" à votre test "If dstProperty IsNot Nothing", pour vous assurer que ce n'est pas une propriété en lecture seule.
JamesMLV

@JamesMLV - merci bonne prise. 'après le fait' - il ne semble pas que l'OP accepte une réponse de toute façon :- (il n'y a donc aucun fait à être après.
Eh

4

L'instance de l'objet doit être créée à l'aide du type de la classe enfant, vous ne pouvez pas convertir une instance de type parent en type enfant


2

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


2

À 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!


1

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 ...


1

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.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.