Nie można przesyłać z klasy nadrzędnej do klasy podrzędnej


100

Próbuję rzutować z klasy nadrzędnej do klasy podrzędnej, ale otrzymuję InvalidCastException. Klasa potomna ma tylko jedną właściwość typu int. Czy ktoś wie, co muszę zrobić?


Warto również wiedzieć, że nie można używać jawnego rzutowania dla powiązanych klas bazowych / pochodnych.
Rzassar

Odpowiedzi:


132

Prostym sposobem na obniżenie w C # jest serializacja elementu nadrzędnego, a następnie deserializacja go do elementu podrzędnego.

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

Mam app konsoli proste, że zwierzę rzuca się psem, stosując powyższe dwie linijki kodu na tutaj


22
Cóż, wahałbym się nazwać to „przygnębionym”.
Kirk Woll,

Uwaga, nazwy zmiennych nie są takie same powyżej.
Jake Gaston

5
Uwielbiam, gdy ktoś myśli nieszablonowo i ucisza ludzi mówiąc OP, że nie da się tego zrobić (z wyjątkiem jednego lub dwóch trolli)! Dzięki za pomoc w tej sprawie. Próbowałem to
rozgryźć

3
To doskonałe rozwiązanie. Miałem przypadek, w którym moja klasa dziecięca była tylko opakowaniem dla rodzica bez dodatkowych funkcji. Zrobiłem to, więc nie musiałem importować odwołania internetowego do mojej aplikacji, ponieważ znajdowało się ono w mojej bibliotece pomocniczej. To pozwoliło mi przekonwertować rodzica na moją klasę opakowania. Dziękuję Ci!
BrianVPS

1
Jesteś geniuszem! :)
Yablargo

121

Nie możesz rzucić ssaka na psa - może to być kot.

Nie możesz wrzucić jedzenia do kanapki - może to być cheeseburger.

Nie możesz wrzucić samochodu do Ferrari - może to być Honda, a dokładniej, Nie możesz rzucić Ferrari 360 Modena na Ferrari 360 Challange Stradale - istnieją różne części, mimo że oba są Ferrari 360.


17
Zrozumiałe blokady drogowe, stąd niemożność rzeczywistego „rzucania” w ten sposób. Ale co, jeśli chce psa, który ma ten sam kolor oczu / wagę / wzór sierści / wiek itp., Co kot trzymany w obiekcie ssaka? Zasadniczo kopiowanie wspólnych właściwości.
FastAl

7
FastAl, właśnie dlatego mamy interfejsy. Mammal musi zaimplementować IMammal i zawierać kolor oczu, wagę itp. Teraz możesz rzucać do IMammal zarówno psa, jak i kota.
Tom Deloford

1
Możesz przelać ssaka do psa. Jeśli to pies, to jest to pies. W przeciwnym razie staje się zerowy. Funkcje „przeciążenia” mogą uniemożliwić konwersję z kota na psa, jeśli cat ma te przeciążone funkcje, które na to pozwalają. Ale Twoim zadaniem jest poradzenie sobie z utratą danych i dostosowanie nieistniejących danych. Na przykład zamiana pazurów w gwoździe, gonienie sznurka w goniącą piłkę itp.
TamusJRoyce

Myślę, że przykłady są nieco ekstremalne i selektywne i być może obsada jest skrótem dla konstruktora kopii. Na przykład budowanie ferrari z właściwościami zdefiniowanymi w samochodzie podstawowym. Lub zacznij od człowieka i stwórz chłopca. Casting i bezpośrednie użycie? Zgadzam się, że nie, nie. Ale jeśli jest to część konstruktora czy coś, może zadziałać. Poniższa odpowiedź dotycząca serializacji to miły akcent.
sirthomas

1
Analogia Ferrari NICE
Lord Darth Vader

59

Instancja, do której odwołuje się odwołanie do klasy bazowej, nie jest instancją klasy podrzędnej. Nie ma nic złego.

Dokładniej:

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

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

Aby rzutowanie zakończyło się sukcesem, instancja, którą obniżasz, musi być instancją klasy, do której dokonujesz downcastingu (lub przynajmniej klasa, do której chcesz downcastować, musi znajdować się w hierarchii klas instancji), w przeciwnym razie rzut nie powiedzie się.


lub potencjalnie Base otherDerived = new OtherDerived (); Pochodne inneFail = (Pochodne) inneDerived;
Blair Conrad,

class Base {} class Derived: Base {} // W głównej metodzie Base DerivedInstance = new Derived (); Base baseInstance = new Base (); Pochodne dobro = (Pochodne) pochodneInstance; Derived fail = (Derived) baseInstance; To kompiluje się bez żadnego błędu w .NET 3.5. Gdzie jest problem, o którym mówisz?
pradeeptp

8
@pradeeptp: Oczywiście, że buduje. Kto powiedział coś o błędzie kompilacji?
Greg D.

18

Widziałem większość ludzi, którzy mówili , że casting do dziecka nie jest możliwy, to w rzeczywistości nie jest prawda. Weźmy poprawiony początek i spróbujmy to udowodnić na przykładach.

Jak wiemy w .net, wszystkie castingi dzielą się na dwie szerokie kategorie.

  1. Typ wartości
  2. Dla typu odniesienia (w twoim przypadku jego typ odniesienia)

Typ odniesienia ma dalsze trzy główne przypadki sytuacyjne, w których może leżeć dowolny scenariusz.

Dziecko do rodzica (rzutowanie niejawne - zawsze udane)

Przypadek 1. Dziecko do jakiegokolwiek bezpośredniego lub pośredniego rodzica

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

Rodzic dla dziecka (rzutowanie jawne - może się powieść)

Przypadek 2. Zmienna nadrzędna przechowująca obiekt nadrzędny (niedozwolone)

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

Przypadek 3. Zmienna nadrzędna przechowująca obiekt podrzędny (zawsze pomyślne)

Uwaga: Ponieważ obiekty mają charakter polimorficzny, zmienna typu klasy nadrzędnej może przechowywać typ podrzędny.

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

Wniosek: Po przeczytaniu przede wszystkim miej nadzieję, że teraz będzie to miało sens, podobnie jak to, w jaki sposób możliwa jest konwersja rodzica na dziecko (Przypadek 3).

Odpowiedz na pytanie :

Twoja odpowiedź brzmi w przypadku 2. Tam, gdzie widzisz, takie rzucanie nie jest dozwolone przez OOP i próbujesz naruszyć jedną z podstawowych zasad OOP, więc zawsze wybieraj bezpieczną ścieżkę.

Co więcej, aby uniknąć takich wyjątkowych sytuacji .net zaleca używanie operatorów is / as , które pomogą Ci podejmować świadome decyzje i zapewnić bezpieczne rzucanie.


17

Są przypadki, w których taka obsada miałaby sens.
W moim przypadku otrzymywałem klasę BASE przez sieć i potrzebowałem do niej więcej funkcji. Więc wyprowadzenie go, aby poradzić sobie z tym po mojej stronie, ze wszystkimi dzwonkami i gwizdkami, które chciałem, i rzucenie otrzymanej klasy BASE na klasę DERIVED po prostu nie wchodziło w grę (rzuca InvalidCast Wyjątek oczywiście)

Jednym praktycznym , nieszablonowym ROZWIĄZANIEM było zadeklarowanie klasy EXTENSION Helper, która w rzeczywistości NIE dziedziczy klasy BASE, ale WŁĄCZA JĄ jako element członkowski.

public class BaseExtension
{
   Base baseInstance;

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

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

Jeśli masz luźne powiązanie i potrzebujesz tylko kilku dodatkowych funkcji do klasy bazowej bez NAPRAWDĘ absolutnej potrzeby wyprowadzania, może to być szybkie i proste obejście.


Czy mam rację sądząc, że prawdopodobnie chcesz, aby Twój BaseExtensiontutaj był przynajmniej IBasetaki, że możesz go używać w podobnych kontekstach? A może nie był ważny dla twoich potrzeb?
tobriand

czasami włączenie może być odpowiednią substytucją dla dziedziczenia
Vahid Ghadiri

13

To naruszyłoby zasady obiektowe. Powiedziałbym, że eleganckim rozwiązaniem tutaj i w innym miejscu projektu jest użycie struktury mapowania obiektów, takiej jak AutoMapper, do skonfigurowania projekcji.

Oto nieco bardziej złożona konfiguracja, niż jest to konieczne, ale jest wystarczająco elastyczna w większości przypadków:

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

Gdy aplikacja zacznie dzwonić AutoMapperConfiguration.Configure(), możesz wyświetlać w następujący sposób:

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

Właściwości są mapowane zgodnie z konwencją, więc jeśli klasa jest dziedziczona, nazwy właściwości są dokładnie takie same, a mapowanie jest konfigurowane automatycznie. Możesz dodać dodatkowe właściwości, dostosowując konfigurację. Zobacz dokumentację .


Używanie Automappera do mapowania typu z jedną właściwością na inną (jak opisano w OP) jest jak użycie młotka kowalskiego do rozbicia jajka. Dlaczego nie po prostu utworzyć nowy typ pochodny i samodzielnie przypisać jego właściwość (czyli 1 wiersz kodu).
bytedev

9

Paul, nie zapytałeś: „Czy mogę to zrobić” - zakładam, że chcesz wiedzieć, jak to zrobić!

Musieliśmy to zrobić w projekcie - istnieje wiele klas, które konfigurujemy w sposób ogólny tylko raz, a następnie inicjalizujemy właściwości specyficzne dla klas pochodnych. Używam VB, więc moja próbka jest w VB (trudne noogies), ale ukradłem próbkę VB z tej witryny, która ma również lepszą wersję C #:

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

Przykładowy kod:

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

Oczywiście to nie jest tak naprawdę casting. Tworzy nowy obiekt pochodny i kopiuje właściwości z rodzica, pozostawiając puste właściwości podrzędne. To wszystko, co musiałem zrobić i wygląda na to, że to wszystko, co musisz zrobić. Zauważ, że kopiuje tylko właściwości, a nie składowe (zmienne publiczne) w klasie (ale możesz to rozszerzyć, aby to zrobić, jeśli wstydzisz się ujawniać publicznych członków).

Rzutowanie generalnie tworzy 2 zmienne wskazujące na ten sam obiekt (mini tutorial tutaj, proszę nie rzucać na mnie wyjątków w narożnikach). Ma to istotne konsekwencje (ćwiczenie dla czytelnika)!

Oczywiście muszę powiedzieć, dlaczego język nie pozwala ci przejść od bazy do wyprowadzenia instancji, ale robi to w drugą stronę. wyobraź sobie przypadek, w którym możesz wziąć wystąpienie pola tekstowego winforms (pochodną) i zapisać je w zmiennej typu kontrolka Winforms. Oczywiście 'control' może przesuwać obiekt wokół OK i możesz poradzić sobie ze wszystkimi rzeczami 'control-y' dotyczącymi pola tekstowego (np. Top, left, .text). Rzeczy specyficzne dla pola tekstowego (np. .Multiline) nie mogą być widoczne bez rzutowania zmiennej typu „control” wskazującej na pole tekstowe w pamięci, ale nadal jest ona w pamięci.

Teraz wyobraź sobie, że masz kontrolkę i chcesz przypisać do niej zmienną typu pole tekstowe. Kontroli w pamięci brakuje „wielowierszowego” i innych elementów tekstowych. Jeśli spróbujesz się do nich odwołać, formant nie rozwinie w magiczny sposób właściwości wielowierszowej! Właściwość (spójrz na nią jak na zmienną składową, która faktycznie przechowuje wartość - ponieważ jest włączona w pamięci instancji pola tekstowego) musi istnieć. Ponieważ rzucasz, pamiętaj, że musi to być ten sam obiekt, na który wskazujesz. Stąd nie jest to ograniczenie językowe, filozoficznie niemożliwe jest takie postępowanie.


1
Wiem, że jest to po fakcie, ale powinieneś dołączyć „AndAlso dstProperty.CanWrite” do testu „If dstProperty IsNot Nothing”, aby upewnić się, że nie jest to właściwość tylko do odczytu.
JamesMLV

@JamesMLV - dzięki, dobry chwyt. „po fakcie” - i tak nie wygląda na to, żeby OP i tak nie przyjmie żadnej odpowiedzi :-( więc nie ma po fakcie. No cóż.
FastAl

4

Instancja obiektu powinna być utworzona przy użyciu typu klasy podrzędnej, nie można rzutować instancji typu nadrzędnego na typ podrzędny


2

Jak dla mnie wystarczyło skopiować wszystkie pola właściwości z klasy bazowej do rodzica w ten sposób:

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

Uniwersalne rozwiązanie dla każdego obiektu można znaleźć tutaj


2

Począwszy od C # 7.0, możesz użyć słowa kluczowego is, aby to zrobić:

Z tymi klasami zdefiniowanymi:

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

Możesz wtedy zrobić coś takiego jak:

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

Co byłoby równoważne z:

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

Możesz teraz zadzwonić:

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

Jak również

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

W ten sposób możesz być pewien, że Twój downcast będzie ważny i nie zgłosi wyjątku!


1

Aby rzutować, rzeczywisty obiekt musi być typu równego lub pochodzącego z typu, do którego próbujesz rzutować ...

lub, aby określić to w odwrotny sposób, typ, na który próbujesz go rzutować, musi być taki sam, lub klasą bazową, rzeczywisty typ obiektu.

jeśli twój rzeczywisty obiekt jest typu Baseclass , nie możesz rzutować go na klasę pochodną Type ...


1

Odmiana podejścia do serializacji dla osób używających ServiceStack:

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

lub bardziej szczegółowe:

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

Serializacja ServiceStack może być super szybka i wszystko, ale oczywiście nie jest to rozwiązanie dla masowych konwersji w transferach o małych opóźnieniach ani dla bardzo złożonych typów. Jest to prawdopodobnie oczywiste dla każdego, kto korzysta z ServiceStack, ale pomyślałem, że wyjaśnię to w oczekiwaniu na komentarze.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.