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ć?
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ć?
Odpowiedzi:
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
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.
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ę.
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.
Typ odniesienia ma dalsze trzy główne przypadki sytuacyjne, w których może leżeć dowolny scenariusz.
Przypadek 1. Dziecko do jakiegokolwiek bezpośredniego lub pośredniego rodzica
Employee e = new Employee();
Person p = (Person)e; //Allowed
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.
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.
BaseExtensiontutaj był przynajmniej IBasetaki, że możesz go używać w podobnych kontekstach? A może nie był ważny dla twoich potrzeb?
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ę .
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 #:
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.
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
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!
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 ...
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.