Es kann nicht von der Elternklasse zur Kinderklasse gewechselt werden


97

Ich versuche, von einer übergeordneten Klasse in eine untergeordnete Klasse umzuwandeln, erhalte jedoch eine InvalidCastException. Die untergeordnete Klasse hat nur eine Eigenschaft vom Typ int. Weiß jemand, was ich tun muss?


Es ist auch gut zu wissen, dass Sie kein explizites Casting für Basis- / Ableitungsklassen verwenden können.
Rzassar

Antworten:


133

Eine einfache Möglichkeit zum Downcasting in C # besteht darin, das übergeordnete Element zu serialisieren und es dann in das untergeordnete Element zu deserialisieren.

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

Ich habe eine einfache Konsolenanwendung , das Tier in Hund wirft, die oben genannten zwei Zeilen Code mit über hier


19
Nun, ich würde zögern, dies als "Niedergeschlagenheit" zu bezeichnen.
Kirk Woll

Nur eine Anmerkung, die Variablennamen sind oben nicht dieselben.
Jake Gaston

5
Ich liebe es, wenn jemand über den Tellerrand hinaus denkt und die Leute zum Schweigen bringt, die dem OP sagen, dass dies nicht möglich ist (abgesehen von ein oder zwei Trollen)! Vielen Dank für die Unterstützung in diesem Fall. Ich habe versucht, dies in den letzten paar Stunden
herauszufinden

3
Dies ist eine ausgezeichnete Lösung. Ich hatte einen Fall, in dem meine Kinderklasse nur ein Wrapper für einen Elternteil ohne zusätzliche Funktionen war. Ich habe das getan, damit ich die Webreferenz nicht in meine Anwendung importieren musste, da sie sich in meiner Hilfsbibliothek befand. Dadurch konnte ich das übergeordnete Element in meine Wrapper-Klasse konvertieren. Danke dir!
BrianVPS

1
Du bist ein Genie! :)
Yablargo

118

Sie können ein Säugetier nicht in einen Hund verwandeln - es könnte eine Katze sein.

Sie können ein Essen nicht in ein Sandwich werfen - es könnte ein Cheeseburger sein.

Sie können kein Auto in einen Ferrari verwandeln - es könnte ein Honda sein, oder genauer gesagt, Sie können keinen Ferrari 360 Modena in einen Ferrari 360 Challange Stradale verwandeln - es gibt verschiedene Teile, obwohl beide Ferrari 360 sind.


17
Verständliche Straßensperren, daher die Unmöglichkeit, auf diese Weise tatsächlich zu „gießen“. Aber was ist, wenn er einen Hund haben möchte, der die gleiche Augenfarbe / Gewicht / Haarmuster / Alter usw. hat wie die Katze, die im Säugetierobjekt gehalten wird? Im Wesentlichen die gemeinsamen Eigenschaften kopieren.
FastAl

7
FastAl, genau deshalb haben wir Schnittstellen. Säugetiere müssen IMammal implementieren und Augenfarbe, Gewicht usw. enthalten. Jetzt können Sie sowohl Hund als auch Katze zu IMammal werfen.
Tom Deloford

1
Sie können versuchen, Säugetier in Hund zu gießen. Wenn es ein Hund ist, ist es ein Hund. Andernfalls wird es null. "Überlast" -Funktionen können die unmögliche Umwandlung von Katze zu Hund ermöglichen, wenn Katze diese überladenen Funktionen hat, die dies ermöglichen. Es ist jedoch Ihre Aufgabe, den Datenverlust zu bewältigen und die nicht vorhandenen Daten anzupassen. Als würde man Krallen in Nägel verwandeln, Schnur jagen, um Ball zu jagen usw.
TamusJRoyce

Ich denke, die Beispiele sind etwas extrem und selektiv und vielleicht ist die Besetzung eine Abkürzung für einen Kopierkonstruktor. Zum Beispiel einen Ferrari mit den im Basisobjekt car definierten Eigenschaften bauen. Oder beginnen Sie mit einem Menschen und erschaffen Sie einen Jungen. Casting und direkter Gebrauch? Stimmen Sie zu, dass dies ein Nein-Nein ist. Aber wenn es Teil des Konstruktors oder so ist, könnte es funktionieren. Die Serialisierungsantwort unten ist eine nette Geste.
Sirthomas

1
Ferrari-Analogie NICE
Lord Darth Vader

57

Die Instanz, auf die sich Ihre Basisklassenreferenz bezieht, ist keine Instanz Ihrer untergeordneten Klasse. Da ist nichts falsch.

Genauer:

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

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

Damit die Umwandlung erfolgreich ist, muss die Instanz, auf die Sie das Downcasting durchführen, eine Instanz der Klasse sein, auf die Sie das Downcasting durchführen (oder zumindest muss die Klasse, auf die Sie das Downcasting durchführen, innerhalb der Klassenhierarchie der Instanz liegen), andernfalls die Besetzung wird scheitern.


oder möglicherweise Base otherDerived = new OtherDerived (); Abgeleitet otherFail = (Abgeleitet) otherDerived;
Blair Conrad

Klasse Base {} Klasse Derived: Base {} // In der Hauptmethode Base derivativeInstance = new Derived (); Base baseInstance = new Base (); Abgeleitetes Gut = (Abgeleitetes) abgeleitetesInstanz; Abgeleitet fehlgeschlagen = (abgeleitet) baseInstance; Dies wird in .NET 3.5 fehlerfrei kompiliert. Wo ist das Problem, das Sie sagen?
Pradeeptp

7
@pradeeptp: Natürlich baut es. Wer hat etwas über einen Kompilierungsfehler gesagt?
Greg D

17

Es gibt einige Fälle, in denen eine solche Besetzung sinnvoll wäre.
In meinem Fall erhielt ich eine BASE-Klasse über das Netzwerk, und ich brauchte mehr Funktionen dafür. Es war also einfach keine Option, es abzuleiten, um es auf meiner Seite mit all den Schnickschnack zu handhaben, die ich wollte, und die empfangene BASE-Klasse in die DERIVED-Klasse zu verwandeln (Throws InvalidCastException of Course).

Eine praktische Think-out-of-the-box war SOLUTION eine Erweiterung Helper - Klasse zu deklarieren , die war nicht BASE Klasse erbt tatsächlich, aber einschließlich IT als Mitglied.

public class BaseExtension
{
   Base baseInstance;

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

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

Wenn Sie eine lose Kopplung haben und nur ein paar zusätzliche Funktionen für die Basisklasse benötigen, ohne WIRKLICH eine absolute Ableitung zu benötigen, kann dies eine schnelle und einfache Problemumgehung sein.


Habe ich Recht, wenn ich denke, dass Sie wahrscheinlich möchten, dass Ihr BaseExtensionhier zumindest so implementiert wird IBase, dass Sie es in ähnlichen Kontexten verwenden können? Oder war es nicht wichtig für deine Bedürfnisse?
tobriand

Einige Male einschließlich kann ein angemessener Ersatz für die Vererbung sein
Vahid Ghadiri

17

Ich habe die meisten Leute gesehen, die sagten, explizites Casting von Eltern zu Kindern sei nicht möglich, das ist eigentlich nicht wahr. Nehmen wir einen überarbeiteten Anfang und versuchen Sie ihn anhand von Beispielen zu beweisen.

Wie wir in .net wissen, haben alle Castings zwei große Kategorien.

  1. Für Werttyp
  2. Für Referenztyp (in Ihrem Fall Referenztyp)

Der Referenztyp hat drei weitere Hauptsituationen, in denen jedes Szenario liegen kann.

Kind zu Eltern (implizites Casting - Immer erfolgreich)

Fall 1. Kind eines direkten oder indirekten Elternteils

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

Eltern zu Kind (Explizites Casting - Kann erfolgreich sein)

Fall 2. Übergeordnete Variable mit übergeordnetem Objekt (nicht zulässig)

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

Fall 3. Übergeordnete Variable mit untergeordnetem Objekt (immer erfolgreich)

Hinweis: Da Objekte polymorpher Natur sind, kann eine Variable eines übergeordneten Klassentyps einen untergeordneten Typ enthalten.

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

Fazit: Hoffen Sie nach dem Lesen vor allem, dass es jetzt Sinn macht, wie eine Konversion von Eltern zu Kindern möglich ist (Fall 3).

Antwort auf die Frage :

Ihre Antwort ist in Fall 2. Wo Sie sehen können, dass ein solches Casting von OOP nicht erlaubt ist und Sie versuchen, eine der Grundregeln von OOP zu verletzen. Wählen Sie daher immer einen sicheren Pfad.

Um solche Ausnahmesituationen zu vermeiden, hat .net empfohlen, is / as- Operatoren zu verwenden, die Ihnen helfen, fundierte Entscheidungen zu treffen und ein sicheres Casting zu gewährleisten.


13

Das würde gegen objektorientierte Prinzipien verstoßen. Ich würde sagen, dass eine elegante Lösung hier und anderswo im Projekt die Verwendung eines Objektzuordnungsframeworks wie AutoMapper zum Konfigurieren einer Projektion ist.

Hier ist eine etwas komplexere Konfiguration als erforderlich, die jedoch für die meisten Fälle flexibel genug ist:

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>();
        });
    }
}

Wenn die Anwendung den Aufruf startet AutoMapperConfiguration.Configure()und Sie dann wie folgt projizieren können:

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

Eigenschaften werden gemäß Konvention zugeordnet. Wenn die Klasse geerbt wird, sind die Eigenschaftsnamen genau gleich und die Zuordnung wird automatisch konfiguriert. Sie können zusätzliche Eigenschaften hinzufügen, indem Sie die Konfiguration anpassen. Siehe die Dokumentation .


Die Verwendung von Automapper zum Zuordnen eines Typs mit einer einzelnen Eigenschaft zu einer anderen (wie im beschriebenen OP) entspricht der Verwendung eines Vorschlaghammers zum Knacken eines Eies. Warum nicht einfach den abgeleiteten Typ neu erstellen und seine Eigenschaft selbst zuweisen (das ist 1 Codezeile).
Bytedev

9

Paul, du hast nicht gefragt, ob ich es tun kann - ich gehe davon aus, dass du wissen willst, wie es geht!

Wir mussten dies für ein Projekt tun - es gibt viele Klassen, die wir nur einmal generisch eingerichtet haben und dann Eigenschaften initialisieren, die für abgeleitete Klassen spezifisch sind. Ich benutze VB, also ist mein Sample in VB (harte Noogies), aber ich habe das VB-Sample von dieser Seite gestohlen, die auch eine bessere C # -Version hat:

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

Beispielcode:

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

Natürlich ist das nicht wirklich Casting. Es wird ein neues abgeleitetes Objekt erstellt und die Eigenschaften vom übergeordneten Objekt kopiert, wobei die untergeordneten Eigenschaften leer bleiben. Das ist alles was ich tun musste und es klingt wie alles was du tun musst. Beachten Sie, dass nur Eigenschaften kopiert werden, keine Mitglieder (öffentliche Variablen) in der Klasse (Sie können dies jedoch erweitern, wenn Sie sich schämen, öffentliche Mitglieder zu entlarven).

Beim Casting werden im Allgemeinen zwei Variablen erstellt, die auf dasselbe Objekt verweisen (Mini-Tutorial hier, bitte werfen Sie keine Ausnahmen für Eckfälle auf mich). Dies hat erhebliche Konsequenzen (Übung für den Leser)!

Natürlich muss ich sagen, warum die Sprache Sie nicht von der Basis zur Ableitung der Instanz gehen lässt, sondern umgekehrt. Stellen Sie sich einen Fall vor, in dem Sie eine Instanz eines Winforms-Textfelds (abgeleitet) in einer Variablen vom Typ Winforms-Steuerelement speichern können. Natürlich kann das 'Steuerelement' das Objekt in Ordnung verschieben und Sie können alle 'Steuerelement-y'-Dinge über das Textfeld erledigen (z. B. oben, links, .text-Eigenschaften). Das textfeldspezifische Material (z. B. .multiline) kann nicht angezeigt werden, ohne dass die Variable vom Typ 'control' umgewandelt wird, die auf das Textfeld im Speicher zeigt, aber es befindet sich immer noch im Speicher.

Stellen Sie sich nun vor, Sie haben ein Steuerelement und möchten eine Variable vom Typ Textfeld einfügen. Dem Steuerelement im Speicher fehlen "mehrzeilige" und andere Textfelder. Wenn Sie versuchen, auf sie zu verweisen, wird das Steuerelement eine mehrzeilige Eigenschaft nicht auf magische Weise vergrößern! Die Eigenschaft (sehen Sie sie hier wie eine Mitgliedsvariable an, die tatsächlich einen Wert speichert - da sie im Speicher der Textboxinstanz aktiviert ist) muss vorhanden sein. Denken Sie beim Casting daran, dass es sich um dasselbe Objekt handeln muss, auf das Sie zeigen. Daher handelt es sich nicht um eine Sprachbeschränkung, es ist philosophisch unmöglich, dies so zu tun.


1
Ich weiß, dass dies weit nach der Tat ist, aber Sie sollten "AndAlso dstProperty.CanWrite" in Ihren Test "If dstProperty IsNot Nothing" aufnehmen, um sicherzustellen, dass es sich nicht um eine schreibgeschützte Eigenschaft handelt.
JamesMLV

@ JamesMLV - danke guten Fang. 'after the fact' - sieht nicht so aus, als würde das OP sowieso keine Antwort akzeptieren :-( es gibt also keine Tatsache, nach der man
suchen könnte

4

Die Instanz des Objekts sollte mit dem Typ der untergeordneten Klasse erstellt werden. Sie können eine Instanz des übergeordneten Typs nicht in einen untergeordneten Typ umwandeln


2

Für mich war es genug, alle Eigenschaftsfelder von der Basisklasse wie folgt auf das übergeordnete Element zu kopieren:

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;
}

Eine universelle Lösung für jedes Objekt finden Sie hier


2

Ab C # 7.0 können Sie dazu das Schlüsselwort is verwenden:

Mit diesen definierten Klassen:

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

Sie können dann etwas tun wie:

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

Welches wäre gleichbedeutend mit:

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

Sie könnten jetzt anrufen:

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

Ebenso gut wie

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

Auf diese Weise können Sie sicher sein, dass Ihr Downcast gültig ist und keine Ausnahme auslöst!


1

Zur Besetzung, der tatsächliche muss Gegenstand einer Art gleich oder abgeleitet von den Sie umwandeln möchten ...

oder, um es anders auszudrücken, der Typ, in den Sie es umwandeln möchten, muss mit dem tatsächlichen Typ des Objekts oder einer Basisklasse davon identisch sein.

Wenn Ihr tatsächliches Objekt vom Typ Baseclass ist , können Sie es nicht in einen abgeleiteten Klassentyp umwandeln ...


1

Eine Variation des Serialisierungsansatzes für Benutzer von ServiceStack:

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

oder umso ausführlicher:

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

Die Serialisierung von ServiceStack ist zwar sehr schnell, aber dies ist weder eine Lösung für massive Konvertierungen bei Übertragungen mit geringer Latenz noch für hochkomplexe Typen. Das ist wahrscheinlich für jeden offensichtlich, der ServiceStack verwendet, aber ich dachte, ich würde es im Vorgriff auf Kommentare klarstellen.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.