Ebeveyn Sınıfından Çocuk Sınıfına Aktarılamıyor


104

Bir ebeveyn sınıfından bir alt sınıfa geçiş yapmaya çalışıyorum ancak bir InvalidCastException alıyorum. Çocuk sınıfının int türünde yalnızca bir özelliği vardır. Ne yapmam gerektiğini bilen var mı?


Ayrıca temel / türetme ile ilgili sınıflar için açık çevrim kullanamayacağınızı bilmekte fayda var.
Rzassar

Yanıtlar:


137

C # 'da alt yayın yapmanın basit bir yolu, ebeveyni serileştirmek ve ardından onu alt öğe olarak seri durumdan çıkarmaktır.

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

Yukarıdaki iki kod satırını kullanarak hayvanı köpeğe dönüştüren basit bir konsol uygulamam var.


22
Buna "mahzunluk" demekten çekinirim.
Kirk Woll

Sadece bir not, değişken isimleri yukarıdakiyle aynı değil.
Jake Gaston

7
Birinin kutunun dışında düşünmesini ve OP'ye bunun yapılamayacağını söyleyen insanları susturmasını seviyorum (bir veya iki trol hariç)! Bu konudaki yardımınız için teşekkürler. Son birkaç saattir bunu çözmeye çalışıyorum :)
derekmx271

3
Bu mükemmel bir çözüm. Çocuk sınıfımın ek işlevselliği olmayan bir ebeveyn için sadece bir paketleyici olduğu bir vakam vardı. Bunu yaptım, böylece yardımcı kitaplığımda olduğu için web referansını uygulamama aktarmak zorunda kalmadım. Bu, ebeveyni sarmalayıcı sınıfıma dönüştürmeme izin verdi. Teşekkür ederim!
BrianVPS

1
Sen bir dahisin! :)
Yablargo

122

Bir memeliyi köpeğe atamazsınız - bir kedi olabilir.

Bir yemeği sandviçin içine atamazsınız - bu bir çizburger olabilir.

Bir arabayı bir Ferrari'ye çeviremezsiniz - bir Honda olabilir veya daha spesifik olarak, bir Ferrari 360 Modena'yı bir Ferrari 360 Challange Stradale'ye çeviremezsiniz - her ikisi de Ferrari 360'lar olsa bile farklı parçalar vardır.


17
Anlaşılabilir barikatlar, dolayısıyla bu şekilde gerçekten "döküm" yapmanın imkansızlığı. Peki ya memeli nesnesinde tutulan kedi ile aynı göz rengi / ağırlığı / tüy modeli / yaşı olan bir köpek istiyorsa? Esasen ortak özellikleri kopyalamak.
FastAl

7
FastAl, işte tam da bu yüzden arayüzlerimiz var. Memeli IMammal uygulamalı ve göz rengi, ağırlığı vb. İçermelidir. Artık hem köpeği hem de kediyi IMammal'a çevirebilirsiniz.
Tom Deloford

1
Memeliyi köpeğe dönüştürebilirsiniz. Eğer köpekse, köpektir. Aksi takdirde boş olur. "Aşırı yük" fonksiyonları, eğer kedinin buna izin veren bu aşırı yüklenmiş fonksiyonları varsa, kediden köpeğe imkansız dönüşümü mümkün kılabilir. Ancak veri kaybını ele almak ve var olmayan verileri uyarlamak sizin işiniz. Pençeleri
çiviye

Örneklerin biraz aşırı ve seçici olduğunu düşünüyorum ve belki de oyuncu kadrosu bir kopya oluşturucu için bir kısayol. Örneğin, temel nesne arabasında tanımlanan özelliklerle bir ferrari inşa etmek. Veya bir insanla başlayın ve bir Boy yaratın. Döküm ve doğrudan kullanım? Katılıyorum, hayır-hayır. Ama kurucu falan bir parçasıysa işe yarayabilir. Aşağıdaki serileştirme cevabı hoş bir dokunuş.
sirthomas

1
Ferrari benzetmesi GÜZEL
Lord Darth Vader

59

Temel sınıf referansınızın başvurduğu örnek, alt sınıfınızın bir örneği değil. Yanlış bir şey yok.

Daha spesifik olarak:

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

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

Yayınlamanın başarılı olması için, aşağıya doğru tahmin ettiğiniz örnek, aşağıya tahmin ettiğiniz sınıfın bir örneği olmalıdır (veya en azından aşağıya doğru tahmin ettiğiniz sınıf, örneğin sınıf hiyerarşisi içinde olmalıdır), aksi takdirde döküm başarısız olur.


veya potansiyel olarak Base otherDerived = new OtherDerived (); Türetilmiş otherFail = (Türetilmiş) otherDerived;
Blair Conrad

sınıf Temel {} sınıf Türetilmiş: Temel {} // Ana Yöntemde Temel DerivedInstance = new Derived (); Base baseInstance = yeni Base (); Türetilmiş mal = (Türetilmiş) türetilmiş Örnek; Türetilmiş hata = (Türetilmiş) baseInstance; Bu, .NET 3.5'te hatasız olarak derlenir. Söylediğin sorun nerede?
pradeeptp

8
@pradeeptp: Elbette inşa ediyor. Derleme hatası hakkında kim bir şey söyledi?
Greg D

19

İnsanların çoğunun açıkça ebeveynden çocuğa rol almanın mümkün olmadığını söylediğini gördüm , bu aslında doğru değil. Gözden geçirilmiş bir başlangıç ​​yapalım ve örneklerle kanıtlamayı deneyelim.

.Net'te bildiğimiz gibi, tüm dökümlerin iki geniş kategorisi vardır.

  1. Değer türü için
  2. Referans türü için (sizin durumunuzda referans türü)

Referans türünde, herhangi bir senaryonun yatabileceği üç ana durumsal durum vardır.

Çocuktan Ebeveyne (Örtük yayın - Her zaman başarılı)

Vaka 1. Doğrudan veya dolaylı herhangi bir ebeveynin çocuğu

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

Ebeveynden Çocuğa (Açıkça yayınlama - Başarılı olabilir)

Durum 2. Üst nesneyi tutan üst değişken (İzin verilmez)

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

Durum 3. Alt nesneyi tutan üst değişken (Her Zaman Başarılı)

Not: Nesnelerin polimorfik doğası olduğundan, ana sınıf türündeki bir değişkenin bir alt türü tutması mümkündür.

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

Sonuç: Her şeyden önce okuduktan sonra, ebeveynden çocuğa dönüşümün nasıl mümkün olduğu gibi şimdi mantıklı olacağını umuyoruz (Durum 3).

Sorunun Cevabı:

Cevabınız 2. durumda. Bu tür bir atamaya OOP tarafından izin verilmediğini ve OOP'nin temel kurallarından birini ihlal etmeye çalıştığınızı görebildiğiniz yerde her zaman güvenli yolu seçin.

Dahası, kullanılarak tavsiye etti .net böyle istisnai durumlardan kaçınmak için gibidir / o haberdar kararlar alıp güvenli dökümü sağlamak için yardımcı olacaktır operatörler.


Cevabınızın netliğini beğendim. Yalnızca bir sorun, is / as belgesinin bağlantısının 404 vermesidir. Aşağıdaki bağlantı başvurduğunuz şey olur mu? Tip testi ve oyuncular
J Man

18

Böyle bir oyuncu kadrosunun mantıklı olacağı bazı durumlar vardır.
Benim durumum, ağ üzerinden bir BASE sınıfı alıyordum ve bunun için daha fazla özelliğe ihtiyacım vardı. Bu yüzden onu istediğim tüm çan ve ıslıklarla kendi tarafımda işleyecek şekilde türetmek ve alınan BASE sınıfını DERIVED sınıfına aktarmak bir seçenek değildi (Throws InvalidCastException of Course)

Bir pratik düşünce out-of-the-box ÇÖZÜM aslında TABAN sınıfını miras DEĞİLDİ bir YAYIM Yardımcısı sınıf bildirmek için, ama bir üyesi olarak BT DAHİL.

public class BaseExtension
{
   Base baseInstance;

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

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

Eğer gevşek bir bağlantıya sahipseniz ve GERÇEKTEN mutlak bir türetme ihtiyacına sahip olmadan temel sınıfa birkaç ekstra özelliğe ihtiyacınız varsa, bu hızlı ve basit bir çözüm olabilir.


Muhtemelen buranızın BaseExtensionen azından IBasebenzer bağlamlarda kullanabileceğiniz şekilde uygulamasını istediğinizi düşünürken haklı mıyım ? Yoksa ihtiyaçlarınız için önemli değil miydi?
tobriand

dahil bazı zamanlar miras için uygun bir ikame olabilir
Vahid Ghadiri

13

Bu, nesneye yönelik ilkeleri ihlal eder. Burada ve projenin başka yerlerinde şık bir çözümün bir projeksiyonu yapılandırmak için AutoMapper gibi bir nesne haritalama çerçevesi kullanmak olduğunu söyleyebilirim .

İşte gerekli olandan biraz daha karmaşık bir yapılandırma, ancak çoğu durumda yeterince esnek:

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

Uygulama aramayı başlattığında AutoMapperConfiguration.Configure()ve ardından şu şekilde projeksiyon yapabilirsiniz:

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

Özellikler konvansiyonel olarak eşlenir, bu nedenle sınıf miras alınırsa özellik adları tam olarak aynı olur ve eşleme otomatik olarak yapılandırılır. Yapılandırmayı değiştirerek ek özellikler ekleyebilirsiniz. Belgelere bakın .


Automapper'ı tek bir özelliği olan bir türü diğeriyle eşlemek için kullanmak (açıklanan OP gibi), bir yumurtayı kırmak için bir kızak çekiç kullanmak gibidir. Neden türetilmiş türü yenileyip özelliğini kendiniz atamıyorsunuz (1 satır kod).
bytedev

9

Paul, 'Yapabilir miyim' diye sormadın - Bunu nasıl yapacağını bilmek istediğini varsayıyorum !

Bunu bir proje üzerinde yapmak zorundaydık - sadece bir kez genel bir şekilde oluşturduğumuz birçok sınıf var, sonra türetilmiş sınıflara özgü özellikleri başlatıyoruz. Örneğim VB'de olduğu için VB kullanıyorum (zorlu noogies), ancak VB örneğini bu siteden çaldım ve daha iyi bir C # sürümüne sahip:

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

Basit 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

Tabii ki bu gerçekten oyuncu seçimi değil. Yeni bir türetilmiş nesne oluşturmak ve özellikleri ebeveynden kopyalamak, alt özellikleri boş bırakmaktır. Tek yapmam gereken buydu ve tek yapmanız gereken bu. Unutmayın ki sadece özellikleri kopyalar, sınıftaki üyeleri (genel değişkenler) değil (ancak bunu yapmak için, genel üyeleri ortaya çıkarmaktan utanç duyuyorsanız bunu genişletebilirsiniz).

Genel olarak çevrim aynı nesneye işaret eden 2 değişken oluşturur (burada mini eğitim, lütfen bana köşe durum istisnaları atmayın). Bunun önemli sonuçları var (okuyucu için egzersiz)!

Elbette, dilin neden temelden örnek türetmenize izin vermediğini, ancak tam tersini yaptığını söylemeliyim. Winforms metin kutusunun (türetilmiş) bir örneğini alabileceğiniz ve bunu Winforms denetimi türünün bir değişkeninde depolayabileceğiniz bir durum hayal edin. Elbette 'kontrol' nesneyi Tamam etrafında hareket ettirebilir ve metin kutusu ile ilgili tüm 'kontrol' şeylerini (örneğin, üst, sol, .text özellikleri) halledebilirsiniz. Metin kutusuna özgü şeyler (örn., .Multiline) bellekteki metin kutusuna işaret eden 'kontrol' türü değişkeni atmadan görülemez, ancak yine de bellekte oradadır.

Şimdi, bir kontrole sahip olduğunuzu ve metin kutusu türünde bir değişken için büyük harf kullanmak istediğinizi hayal edin. Bellekteki Kontrol, 'çok satırlı' ve diğer metin kutusu şeylerini kaçırıyor. Onlara başvurmaya çalışırsanız, kontrol sihirli bir şekilde çok satırlı bir mülk oluşturmaz! Özellik (buraya bir üye değişkeni gibi bakın, aslında bir değer depolayan - çünkü metin kutusu örneğinin belleğinde bir açık var ) olmalıdır . Döküm yaptığınız için, işaret ettiğiniz nesnenin aynı olması gerektiğini unutmayın. Dolayısıyla bu bir dil kısıtlaması değildir, böyle bir durumda olması felsefi olarak imkansızdır.


1
Bunun gerçeğin çok ötesinde olduğunu biliyorum, ancak salt okunur bir özellik olmadığından emin olmak için "If dstProperty IsNot Nothing" testinize "AndAlso dstProperty.CanWrite" ifadesini eklemelisiniz.
JamesMLV

@JamesMLV - teşekkürler iyi yakaladım. 'olaydan sonra' - OP zaten herhangi bir cevabı kabul edecek gibi görünmüyor :-( yani bundan sonra olacak bir gerçek yok. Oh pekala.
FastAl

4

Nesnenin örneği, alt sınıfın türü kullanılarak oluşturulmalıdır, bir üst tür örneğini bir alt türe çeviremezsiniz


2

Bana gelince, tüm özellik alanlarını temel sınıftan ebeveyne şu şekilde kopyalamak yeterliydi:

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

Herhangi bir nesne için evrensel bir çözüm burada bulunabilir


2

C # 7.0'dan itibaren, bunu yapmak için is anahtar sözcüğünü kullanabilirsiniz:

Tanımlanan sınıflarla:

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

Daha sonra şöyle bir şey yapabilirsiniz:

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

Hangisi şuna eşdeğerdir:

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

Şimdi arayabilirsin:

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

Hem de

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

Bu şekilde, mahzunluğunuzun geçerli olacağından ve bir istisna atmayacağından emin olabilirsiniz!


1

Döküm için, gerçek bir nesne için eşit ya da Türü olmalıdır türetilmiş dan size döküm çalıştığınız Tip ...

veya tersini ifade etmek için, onu dönüştürmeye çalıştığınız Tür, nesnenin gerçek türü ile aynı veya temel bir sınıf olmalıdır.

gerçek nesneniz Baseclass türündeyse , onu türetilmiş bir türe dönüştüremezsiniz Type ...


1

ServiceStack kullananlar için serileştirme yaklaşımının bir varyasyonu:

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

veya daha ayrıntılı:

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

ServiceStack'in serileştirmesi süper hızlı olabilir, ancak açıkça, bu düşük gecikmeli aktarımlarda büyük dönüşümler için veya oldukça karmaşık türler için bir çözüm değildir. Bu muhtemelen ServiceStack kullanan herkes için açıktır, ancak yorum beklentisiyle açıklığa kavuşturacağımı düşündüm.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.