Czy jest możliwe przypisanie obiektu klasy bazowej do odwołania do klasy pochodnej z jawnym rzutowaniem typu w C # ?.
Wypróbowałem to i powoduje to błąd w czasie wykonywania.
c#
casting
derived-class
base-class
downcast
Maddy.Shik
źródło
źródło
Derived
, ale możesz traktowaćDerived
odniesienie jakoBase
odniesienie.Base
, a druga tworzy instancjęDerived
. Jeśli wywołasz metodę wirtualną,b
która została zastąpiona w programieDerived
, zobaczyszDerived
zachowanie, jeśli masz wystąpienieDerived
. Ale nie jest właściwe wchodzenie w szczegóły w wątku komentarzy Stack Overflow - naprawdę powinieneś przeczytać dobrą książkę lub samouczek C #, ponieważ jest to dość fundamentalna sprawa.Nie, nie jest to możliwe, ponieważ przypisanie go do odwołania do klasy pochodnej byłoby jak powiedzenie „Klasa podstawowa jest w pełni zdolnym substytutem klasy pochodnej, może robić wszystko, co może zrobić klasa pochodna”, co nie jest prawdą, ponieważ klasy pochodne w ogólnej ofercie więcej funkcji niż ich klasa bazowa (przynajmniej taka jest idea dziedziczenia).
Możesz napisać konstruktor w klasie pochodnej, pobierając jako parametr obiekt klasy bazowej, kopiując wartości.
Coś takiego:
public class Base { public int Data; public void DoStuff() { // Do stuff with data } } public class Derived : Base { public int OtherData; public Derived(Base b) { this.Data = b.Data; OtherData = 0; // default value } public void DoOtherStuff() { // Do some other stuff } }
W takim przypadku należy skopiować obiekt podstawowy i uzyskać w pełni funkcjonalny obiekt klasy pochodnej z domyślnymi wartościami dla elementów pochodnych. W ten sposób możesz również uniknąć problemu wskazanego przez Jona Skeeta:
Base b = new Base();//base class Derived d = new Derived();//derived class b.DoStuff(); // OK d.DoStuff(); // Also OK b.DoOtherStuff(); // Won't work! d.DoOtherStuff(); // OK d = new Derived(b); // Copy construct a Derived with values of b d.DoOtherStuff(); // Now works!
źródło
Miałem ten problem i rozwiązałem go, dodając metodę, która przyjmuje parametr typu i konwertuje bieżący obiekt na ten typ.
public TA As<TA>() where TA : Base { var type = typeof (TA); var instance = Activator.CreateInstance(type); PropertyInfo[] properties = type.GetProperties(); foreach (var property in properties) { property.SetValue(instance, property.GetValue(this, null), null); } return (TA)instance; }
Oznacza to, że możesz go użyć w swoim kodzie w następujący sposób:
var base = new Base(); base.Data = 1; var derived = base.As<Derived>(); Console.Write(derived.Data); // Would output 1
źródło
Jak wielu innych odpowiedziało, nie.
Używam poniższego kodu w tych niefortunnych sytuacjach, gdy muszę użyć typu podstawowego jako typu pochodnego. Tak, jest to naruszenie zasady substytucji Liskova (LSP) i tak, w większości przypadków wolimy skład, a nie dziedziczenie. Podziękowania dla Markusa Knappena Johanssona, na którym opiera się pierwotna odpowiedź.
Ten kod w klasie bazowej:
public T As<T>() { var type = typeof(T); var instance = Activator.CreateInstance(type); if (type.BaseType != null) { var properties = type.BaseType.GetProperties(); foreach (var property in properties) if (property.CanWrite) property.SetValue(instance, property.GetValue(this, null), null); } return (T) instance; }
Umożliwia:
Ponieważ wykorzystuje odbicie, jest „drogi”. Użyj odpowiednio.
źródło
user-defined conversions to or from a base class are not allowed
widzę powody takiego stanu rzeczy , ale jestem rozczarowany, ponieważ byłoby fajnie, gdyby to pozwoliło ..if (type.BaseType != null)
oświadczenie odnoszące się do A. Markusa Knappena Johanssona. Dlaczego tak jest? Oznacza to, że pozwoliłoby na Typ w wywołaniach, który nie jest pochodną MyBaseClass (ani niczego innego). Zdaję sobie sprawę, że nadal spowoduje to błąd kompilatora, jeśli zostanie przypisany do myDerivedObject, ale jeśli zostanie użyty tylko jako Expression, będzie się kompilował iw czasie wykonywania po prostu utworzy myDerivedObject bez żadnych danych skopiowanych z „myBaseObject”. Nie wyobrażam sobie zastosowania tego.Nie, nie jest to możliwe, stąd błąd wykonania.
Możesz jednak przypisać wystąpienie klasy pochodnej do zmiennej typu klasy bazowej.
źródło
Rozwiązanie z JsonConvert (zamiast typecast)
Dzisiaj napotkałem ten sam problem i znalazłem proste i szybkie rozwiązanie problemu za pomocą
JsonConvert
.var base = new BaseClass(); var json = JsonConvert.SerializeObject(base); DerivedClass derived = JsonConvert.DeserializeObject<DerivedClass>(json);
źródło
Jak wszyscy tutaj mówili, nie jest to bezpośrednio możliwe.
Metoda, którą preferuję i jest raczej czysta, polega na użyciu programu do mapowania obiektów, takiego jak AutoMapper .
Wykonuje zadanie automatycznego kopiowania właściwości z jednej instancji do drugiej (niekoniecznie tego samego typu).
źródło
Rozwinięcie odpowiedzi @ ybo - nie jest możliwe, ponieważ instancja klasy bazowej nie jest w rzeczywistości instancją klasy pochodnej. Wie tylko o elementach klasy bazowej i nie wie nic o elementach klasy pochodnej.
Powodem, dla którego można rzutować wystąpienie klasy pochodnej na wystąpienie klasy bazowej, jest to, że klasa pochodna w rzeczywistości jest już wystąpieniem klasy bazowej, ponieważ ma już te elementy członkowskie. Nie można powiedzieć, że jest inaczej.
źródło
Można rzutować zmienną wpisaną jako klasę bazową na typ klasy pochodnej; jednak z konieczności spowoduje to sprawdzenie w czasie wykonywania, aby zobaczyć, czy rzeczywisty obiekt, którego to dotyczy, jest odpowiedniego typu.
Po utworzeniu nie można zmienić typu obiektu (co więcej, może nie być tego samego rozmiaru). Możesz jednak przekonwertować wystąpienie, tworząc nowe wystąpienie drugiego typu - ale musisz ręcznie napisać kod konwersji.
źródło
Nie, to niemożliwe.
Rozważmy scenariusz, w którym ACBus jest klasą pochodną klasy bazowej Bus. ACBus ma funkcje takie jak TurnOnAC i TurnOffAC, które działają na polu o nazwie ACState. TurnOnAC włącza ACState, a TurnOffAC wyłącza ACState. Jeśli spróbujesz użyć funkcji TurnOnAC i TurnOffAC w Bus, nie ma to sensu.
źródło
class Program { static void Main(string[] args) { a a1 = new b(); a1.print(); } } class a { public a() { Console.WriteLine("base class object initiated"); } public void print() { Console.WriteLine("base"); } } class b:a { public b() { Console.WriteLine("child class object"); } public void print1() { Console.WriteLine("derived"); } }
}
kiedy tworzymy obiekt klasy podrzędnej, obiekt klasy bazowej jest inicjowany automatycznie, więc zmienna referencyjna klasy bazowej może wskazywać obiekt klasy potomnej.
ale nie odwrotnie, ponieważ zmienna referencyjna klasy potomnej nie może wskazywać na obiekt klasy bazowej, ponieważ nie jest tworzony żaden obiekt klasy potomnej.
a także zauważ, że zmienna referencyjna klasy bazowej może wywoływać tylko składową klasy bazowej.
źródło
Właściwie JEST na to sposób. Pomyśl o tym, jak możesz użyć Newtonsoft JSON do deserializacji obiektu z json. Zignoruje (lub przynajmniej może) zignorować brakujące elementy i zapełni wszystkie elementy, o których wie.
Oto jak to zrobiłem. Po moim wyjaśnieniu nastąpi mały przykład kodu.
Utwórz wystąpienie obiektu z klasy bazowej i odpowiednio ją wypełnij.
Używając klasy „jsonconvert” biblioteki Newtonsoft json, zserializuj ten obiekt w łańcuch json.
Teraz utwórz obiekt podklasy przez deserializację za pomocą łańcucha json utworzonego w kroku 2. Spowoduje to utworzenie wystąpienia Twojej podklasy ze wszystkimi właściwościami klasy bazowej.
To działa jak urok! Więc… kiedy jest to przydatne? Niektórzy pytali, kiedy to ma sens, i sugerowali zmianę schematu OP, aby uwzględnić fakt, że nie można tego natywnie zrobić z dziedziczeniem klas (w .Net).
W moim przypadku mam klasę ustawień, która zawiera wszystkie „podstawowe” ustawienia usługi. Konkretne usługi mają więcej opcji, a te pochodzą z innej tabeli DB, więc te klasy dziedziczą klasę bazową. Wszystkie mają inny zestaw opcji. Dlatego podczas pobierania danych dla usługi znacznie łatwiej jest PIERWSZE wypełnić wartości przy użyciu wystąpienia obiektu podstawowego. Jedna metoda, aby to zrobić za pomocą pojedynczego zapytania DB. Zaraz po tym tworzę obiekt podklasy przy użyciu metody opisanej powyżej. Następnie wykonuję drugie zapytanie i wypełniam wszystkie wartości dynamiczne obiektu podklasy.
Końcowym wynikiem jest klasa pochodna z ustawionymi wszystkimi opcjami. Powtórzenie tego dla dodatkowych nowych podklas zajmuje tylko kilka linii kodu. Jest to proste i wykorzystuje bardzo wypróbowany i przetestowany pakiet (Newtonsoft), aby magia działała.
Ten przykładowy kod to vb.Net, ale można go łatwo przekonwertować na język C #.
' First, create the base settings object. Dim basePMSettngs As gtmaPayMethodSettings = gtmaPayments.getBasePayMethodSetting(payTypeId, account_id) Dim basePMSettingsJson As String = JsonConvert.SerializeObject(basePMSettngs, Formatting.Indented) ' Create a pmSettings object of this specific type of payment and inherit from the base class object Dim pmSettings As gtmaPayMethodAimACHSettings = JsonConvert.DeserializeObject(Of gtmaPayMethodAimACHSettings)(basePMSettingsJson)
źródło
var destObject = JsonConvert.DeserializeObject<DestinationType>(JsonConvert.SerializeObject(srcObject));
. Używałbym tego tylko do testów jednostkowych i innych nieprodukcyjnych "hacków"!Możesz użyć rozszerzenia:
public static void CopyOnlyEqualProperties<T>(this T objDest, object objSource) where T : class { foreach (PropertyInfo propInfo in typeof(T).GetProperties()) if (objSource.GetType().GetProperties().Any(z => z.Name == propInfo.Name && z.GetType() == propInfo.GetType())) propInfo.SetValue(objDest, objSource.GetType().GetProperties().First(z => z.Name == propInfo.Name && z.GetType() == propInfo.GetType()).GetValue(objSource)); }
W kodzie:
public class BaseClass { public string test{ get; set;} } public Derived : BaseClass { //Some properies } public void CopyProps() { BaseClass baseCl =new BaseClass(); baseCl.test="Hello"; Derived drv=new Derived(); drv.CopyOnlyEqualProperties(baseCl); //Should return Hello to the console now in derived class. Console.WriteLine(drv.test); }
źródło
Może nie mieć znaczenia, ale mogłem uruchomić kod na obiekcie pochodnym, biorąc pod uwagę jego podstawę. Jest zdecydowanie bardziej hakerski niż bym chciał, ale działa:
public static T Cast<T>(object obj) { return (T)obj; }
...
//Invoke parent object's json function MethodInfo castMethod = this.GetType().GetMethod("Cast").MakeGenericMethod(baseObj.GetType()); object castedObject = castMethod.Invoke(null, new object[] { baseObj }); MethodInfo jsonMethod = baseObj.GetType ().GetMethod ("ToJSON"); return (string)jsonMethod.Invoke (castedObject,null);
źródło
Możesz to zrobić za pomocą generic.
public class BaseClass { public int A { get; set; } public int B { get; set; } private T ConvertTo<T>() where T : BaseClass, new() { return new T { A = A, B = B } } public DerivedClass1 ConvertToDerivedClass1() { return ConvertTo<DerivedClass1>(); } public DerivedClass2 ConvertToDerivedClass2() { return ConvertTo<DerivedClass2>(); } } public class DerivedClass1 : BaseClass { public int C { get; set; } } public class DerivedClass2 : BaseClass { public int D { get; set; } }
Dzięki temu podejściu zyskujesz trzy korzyści.
źródło
Wiem, że to jest stare, ale używam go z powodzeniem od dłuższego czasu.
private void PopulateDerivedFromBase<TB,TD>(TB baseclass,TD derivedclass) { //get our baseclass properties var bprops = baseclass.GetType().GetProperties(); foreach (var bprop in bprops) { //get the corresponding property in the derived class var dprop = derivedclass.GetType().GetProperty(bprop.Name); //if the derived property exists and it's writable, set the value if (dprop != null && dprop.CanWrite) dprop.SetValue(derivedclass,bprop.GetValue(baseclass, null),null); } }
źródło
Połączyłem niektóre fragmenty poprzednich odpowiedzi (dzięki tym autorom) i złożyłem prostą statyczną klasę z dwiema metodami, których używamy.
Tak, to proste, nie, nie obejmuje wszystkich scenariuszy, tak, można go rozszerzyć i ulepszyć, nie, nie jest idealne, tak, można by było bardziej wydajnie, nie to nie jest najlepsza rzecz od czasu krojonego chleba, tak jest w pełni funkcjonalne, solidne mapery obiektów pakietu nuget, które są o wiele lepsze do intensywnego użytku itp., yada yada - ale działa jednak dla naszych podstawowych potrzeb :)
I oczywiście spróbuje odwzorować wartości z dowolnego obiektu na dowolny obiekt, pochodny lub nie (oczywiście tylko właściwości publiczne, które mają takie same nazwy - ignoruje resztę).
STOSOWANIE:
SesameStreetCharacter puppet = new SesameStreetCharacter() { Name = "Elmo", Age = 5 }; // creates new object of type "RealPerson" and assigns any matching property // values from the puppet object // (this method requires that "RealPerson" have a parameterless constructor ) RealPerson person = ObjectMapper.MapToNewObject<RealPerson>(puppet); // OR // create the person object on our own // (so RealPerson can have any constructor type that it wants) SesameStreetCharacter puppet = new SesameStreetCharacter() { Name = "Elmo", Age = 5 }; RealPerson person = new RealPerson("tall") {Name = "Steve"}; // maps and overwrites any matching property values from // the puppet object to the person object so now our person's age will get set to 5 and // the name "Steve" will get overwritten with "Elmo" in this example ObjectMapper.MapToExistingObject(puppet, person);
STATYCZNA KLASA UŻYTKOWA:
public static class ObjectMapper { // the target object is created on the fly and the target type // must have a parameterless constructor (either compiler-generated or explicit) public static Ttarget MapToNewObject<Ttarget>(object sourceobject) where Ttarget : new() { // create an instance of the target class Ttarget targetobject = (Ttarget)Activator.CreateInstance(typeof(Ttarget)); // map the source properties to the target object MapToExistingObject(sourceobject, targetobject); return targetobject; } // the target object is created beforehand and passed in public static void MapToExistingObject(object sourceobject, object targetobject) { // get the list of properties available in source class var sourceproperties = sourceobject.GetType().GetProperties().ToList(); // loop through source object properties sourceproperties.ForEach(sourceproperty => { var targetProp = targetobject.GetType().GetProperty(sourceproperty.Name); // check whether that property is present in target class and is writeable if (targetProp != null && targetProp.CanWrite) { // if present get the value and map it var value = sourceobject.GetType().GetProperty(sourceproperty.Name).GetValue(sourceobject, null); targetobject.GetType().GetProperty(sourceproperty.Name).SetValue(targetobject, value, null); } }); } }
źródło
Możesz użyć konstruktora kopiującego, który natychmiast wywołuje konstruktor instancji lub jeśli konstruktor instancji wykonuje więcej niż przypisania, konstruktor kopiujący przypisuje przychodzące wartości do instancji.
class Person { // Copy constructor public Person(Person previousPerson) { Name = previousPerson.Name; Age = previousPerson.Age; } // Copy constructor calls the instance constructor. public Person(Person previousPerson) : this(previousPerson.Name, previousPerson.Age) { } // Instance constructor. public Person(string name, int age) { Name = name; Age = age; } public int Age { get; set; } public string Name { get; set; } }
Odwołano się do dokumentacji Microsoft C # w obszarze Konstruktor dla tego przykładu, mając ten problem w przeszłości.
źródło
Innym rozwiązaniem jest dodanie metody rozszerzenia w następujący sposób:
public static void CopyProperties(this object destinationObject, object sourceObject, bool overwriteAll = true) { try { if (sourceObject != null) { PropertyInfo[] sourceProps = sourceObject.GetType().GetProperties(); List<string> sourcePropNames = sourceProps.Select(p => p.Name).ToList(); foreach (PropertyInfo pi in destinationObject.GetType().GetProperties()) { if (sourcePropNames.Contains(pi.Name)) { PropertyInfo sourceProp = sourceProps.First(srcProp => srcProp.Name == pi.Name); if (sourceProp.PropertyType == pi.PropertyType) if (overwriteAll || pi.GetValue(destinationObject, null) == null) { pi.SetValue(destinationObject, sourceProp.GetValue(sourceObject, null), null); } } } } } catch (ApplicationException ex) { throw; } }
następnie miej konstruktora w każdej klasie pochodnej, która akceptuje klasę bazową:
public class DerivedClass: BaseClass { public DerivedClass(BaseClass baseModel) { this.CopyProperties(baseModel); } }
Opcjonalnie nadpisze właściwości miejsca docelowego, jeśli jest już ustawione (nie jest puste) lub nie.
źródło
Możliwe są nie tylko jawne, ale także niejawne konwersje.
Język C # nie zezwala na takie operatory konwersji, ale nadal można je pisać w czystym C # i działają. Zauważ, że klasa, która definiuje niejawny operator konwersji (
Derived
) i klasa, która używa operatora (Program
), muszą być zdefiniowane w oddzielnych zespołach (np.Derived
Klasa znajduje się w a, dolibrary.dll
którego odwołuje sięprogram.exe
zawierającProgram
klasę).//In library.dll: public class Base { } public class Derived { [System.Runtime.CompilerServices.SpecialName] public static Derived op_Implicit(Base a) { return new Derived(a); //Write some Base -> Derived conversion code here } [System.Runtime.CompilerServices.SpecialName] public static Derived op_Explicit(Base a) { return new Derived(a); //Write some Base -> Derived conversion code here } } //In program.exe: class Program { static void Main(string[] args) { Derived z = new Base(); //Visual Studio can show squiggles here, but it compiles just fine. } }
Gdy odwołujesz się do biblioteki przy użyciu odwołania do projektu w programie Visual Studio, VS pokazuje zawijasy podczas korzystania z niejawnej konwersji, ale kompiluje się dobrze. Jeśli odwołasz się tylko do
library.dll
, nie ma zawijasów.źródło
System.Runtime.CompilerServices.SpecialName
Attribute? Dokumenty dla każdej wersji od najwcześniejszej dostępnej (2.0) do „aktualnej wersji” (4.6? „Ktoś? Ktokolwiek?”) Nie mówią, co robi, ale mówią „Klasa SpecialNameAttribute nie jest obecnie używana w .NET Framework, ale jest zarezerwowany do użytku w przyszłości. ”. Zobacz: [link] ( msdn.microsoft.com/en-us/library/ms146064(v=vs.100).aspx ).where T : Delegate
lub sparametryzowane właściwości, takie jak indeksatory itp. Itp.).what does System.Runtime.CompilerServices.SpecialName Attribute do?
- Służy do oznaczania metod tworzonych przez pewne specjalne wygodne konstrukcje języków .Net wysokiego poziomu: metody dostępu do właściwości, akcesory zdarzeń, konstruktory, operatory, indeksatory, itp. O ile metoda IL nie jest oznaczonaspecialname
, nie będzie widoczna jako właściwość / zdarzenie / konstruktor i zostanie rozpoznany jako normalna metoda. Ręczne oznaczanie metod o odpowiednich nazwach tym atrybutem to po prostu ręczne wykonanie części pracy kompilatora.op_Exponent
metodę i oznaczyć jąspecialname
atrybutem.Co powiesz na:
public static T As<T>(this object obj) { return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(obj)); }
źródło
Najlepszym sposobem dodania wszystkich właściwości podstawowych do elementu pochodnego jest użycie odbicia w costructor. Wypróbuj ten kod bez tworzenia metod lub instancji.
public Derived(Base item) :base() { Type type = item.GetType(); System.Reflection.PropertyInfo[] properties = type.GetProperties(); foreach (var property in properties) { try { property.SetValue(this, property.GetValue(item, null), null); } catch (Exception) { } } }
źródło
Nie zgadzam się, że to niemożliwe. Możesz to zrobić w ten sposób:
public class Auto { public string Make {get; set;} public string Model {get; set;} } public class Sedan : Auto { public int NumberOfDoors {get; set;} } public static T ConvertAuto<T>(Sedan sedan) where T : class { object auto = sedan; return (T)loc; }
Stosowanie:
var sedan = new Sedan(); sedan.NumberOfDoors = 4; var auto = ConvertAuto<Auto>(sedan);
źródło
var auto =
nadal jest typusedan
Tak rozwiązałem to dla pól. Jeśli chcesz, możesz wykonać tę samą iterację poprzez właściwości. Możesz chcieć sprawdzić
null
itp., Ale taki jest pomysł.public static DerivedClass ConvertFromBaseToDerived<BaseClass, DerivedClass>(BaseClass baseClass) where BaseClass : class, new() where DerivedClass : class, BaseClass, new() { DerivedClass derived = (DerivedClass)Activator.CreateInstance(typeof(DerivedClass)); derived.GetType().GetFields().ToList().ForEach(field => { var base_ = baseClass.GetType().GetField(field.Name).GetValue(baseClass); field.SetValue(derived, base_); }); return derived; }
źródło
Możesz po prostu serializować obiekt podstawowy do formatu JSON, a następnie deserializować go do obiektu pochodnego.
źródło
Nie w tradycyjnym sensie ... Konwertuj na Json, potem na swój obiekt i bum, gotowe! Jesse powyżej opublikował odpowiedź jako pierwszą, ale nie używał tych metod rozszerzających, które znacznie ułatwiają proces. Utwórz kilka metod rozszerzających:
public static string ConvertToJson<T>(this T obj) { return JsonConvert.SerializeObject(obj); } public static T ConvertToObject<T>(this string json) { if (string.IsNullOrEmpty(json)) { return Activator.CreateInstance<T>(); } return JsonConvert.DeserializeObject<T>(json); }
Umieść je w swojej skrzynce narzędziowej na zawsze, wtedy zawsze możesz to zrobić:
var derivedClass = baseClass.ConvertToJson().ConvertToObject<derivedClass>();
Ach, moc JSON.
Jest kilka pułapek związanych z tym podejściem: naprawdę tworzymy nowy obiekt, a nie rzucamy, co może, ale nie musi, mieć znaczenie. Pola prywatne nie zostaną przeniesione, konstruktory z parametrami nie zostaną wywołane itp. Możliwe, że nie zostanie przypisany jakiś potomny plik json. Strumienie nie są wewnętrznie obsługiwane przez JsonConvert. Jeśli jednak nasza klasa nie opiera się na prywatnych polach i konstruktorach, jest to bardzo skuteczna metoda przenoszenia danych z klasy do klasy bez mapowania i wywoływania konstruktorów, co jest głównym powodem, dla którego chcemy rzutować w pierwszej kolejności.
źródło
Nie, zobacz to pytanie, które zadałem - Upcasting w .NET przy użyciu generycznych
Najlepszym sposobem jest utworzenie domyślnego konstruktora w klasie, skonstruowanie, a następnie wywołanie
Initialise
metodyźródło