Tidak dapat Mentransmisikan dari Kelas Orang Tua ke Kelas Anak


101

Saya mencoba untuk mentransmisikan dari kelas induk ke kelas anak tetapi saya mendapatkan InvalidCastException. Kelas anak hanya memiliki satu properti tipe int. Adakah yang tahu apa yang harus saya lakukan?


Baik juga untuk mengetahui bahwa, Anda tidak dapat menggunakan casting eksplisit untuk kelas terkait base / derive.
Rzassar

Jawaban:


133

Cara sederhana untuk downcast di C # adalah untuk membuat serial orang tua dan kemudian deserialisasi ke dalam anak.

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

Saya memiliki aplikasi konsol sederhana yang mengubah hewan menjadi anjing, menggunakan dua baris kode di atas di sini


22
Yah, saya akan ragu untuk menyebut ini "sedih".
Kirk Woll

Sekadar catatan, nama variabel di atas tidak sama.
Jake Gaston

5
Saya suka ketika seseorang berpikir di luar kotak dan membungkam orang-orang yang memberi tahu OP bahwa itu tidak dapat dilakukan (kecuali satu atau dua troll)! Terima kasih atas bantuannya. Saya sudah mencoba mencari tahu hal ini selama beberapa jam terakhir :)
derekmx271

3
Ini adalah solusi yang bagus. Saya punya kasus di mana kelas anak saya hanyalah pembungkus untuk orang tua tanpa fungsi tambahan. Saya melakukan itu sehingga saya tidak perlu mengimpor referensi web ke dalam aplikasi saya karena itu ada di perpustakaan pembantu saya. Ini memungkinkan saya untuk mengubah induk ke kelas pembungkus saya. Terima kasih!
BrianVPS

1
Anda jenius! :)
Yablargo

121

Anda tidak dapat mengubah mamalia menjadi seekor anjing - itu mungkin seekor kucing.

Anda tidak dapat memasukkan makanan ke dalam sandwich - itu mungkin burger keju.

Anda tidak dapat memasukkan mobil ke dalam Ferrari - itu mungkin Honda, atau lebih khusus lagi, Anda tidak dapat mentransmisikan Ferrari 360 Modena ke Ferrari 360 Challange Stradale - ada bagian yang berbeda, meskipun keduanya adalah Ferrari 360.


17
Hambatan yang bisa dimengerti, oleh karena itu tidak mungkin untuk benar-benar 'mentransmisi' dengan cara ini. Tetapi bagaimana jika dia menginginkan seekor anjing yang memiliki warna mata / berat / pola rambut / umur yang sama, dll. Dengan kucing yang sedang dipeluk di objek mamalia? Pada dasarnya menyalin properti umum.
FastAl

7
FastAl, inilah mengapa kami memiliki antarmuka. Mamalia harus menerapkan IMammal dan mengandung warna mata, bobot dll. Sekarang Anda dapat melemparkan anjing dan kucing ke IMammal.
Tom Deloford

1
Anda dapat mengubah mamalia menjadi anjing. Jika itu seekor anjing, itu seekor anjing. Jika tidak, itu menjadi nol. Fungsi "kelebihan beban" dapat membuat konversi yang tidak mungkin dari kucing ke anjing menjadi mungkin, jika kucing memiliki fungsi kelebihan beban yang memungkinkan hal ini. Tetapi tugas Anda adalah menangani kehilangan data dan menyesuaikan data yang tidak ada. Seperti mengubah cakar menjadi paku, mengejar tali menjadi mengejar bola, dll ...
TamusJRoyce

Saya pikir contohnya agak ekstrim dan selektif dan mungkin pemerannya adalah jalan pintas untuk pembuat salinan. Misalnya, sedang membangun ferrari dengan properti yang ditentukan di mobil objek dasar. Atau, mulai dengan manusia dan ciptakan seorang Anak Laki-laki. Mentransmisi dan menggunakan langsung? Setuju itu tidak-tidak. Tetapi jika itu bagian dari konstruktor atau sesuatu, bisa berfungsi. Jawaban serialisasi di bawah ini adalah sentuhan yang bagus.
sirthomas

1
Analogi Ferrari BAGUS
Lord Darth Vader

59

Instance yang dirujuk oleh referensi kelas dasar Anda bukanlah instance dari kelas anak Anda. Tidak ada yang salah.

Lebih spesifik:

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

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

Agar pemeran berhasil, instance yang Anda downcast haruslah instance dari kelas yang Anda downcasting (atau setidaknya, kelas yang Anda downcasting harus berada dalam hierarki kelas instance), jika tidak, cast akan gagal.


atau kemungkinan Base otherDerived = new OtherDerived (); Derived otherFail = (Derived) otherDerived;
Blair Conrad

kelas Basis {} kelas Berasal: Basis {} // Dalam Metode Utama Basis turunanInstance = baru Derived (); Base baseInstance = new Base (); Barang turunan = (Turunan) turunan Instansi; Derivasi gagal = (Turunan) baseInstance; Ini mengkompilasi tanpa kesalahan apa pun di .NET 3.5. Dimana masalah yang anda katakan?
pradeeptp

8
@pradeeptp: Tentu saja itu membangun. Siapa yang mengatakan tentang kesalahan kompilasi?
Greg D

18

Saya telah melihat sebagian besar orang mengatakan bahwa casting eksplisit dari orang tua ke anak tidak mungkin, itu sebenarnya tidak benar. Mari kita mulai dengan revisi dan coba buktikan dengan contoh.

Seperti yang kita ketahui di .net semua casting memiliki dua kategori besar.

  1. Untuk tipe Nilai
  2. Untuk tipe Referensi (dalam kasus Anda tipe referensinya)

Jenis referensi memiliki tiga kasus situasional utama di mana setiap skenario dapat berbohong.

Anak ke Orang Tua (Transmisi implisit - Selalu berhasil)

Kasus 1. Anak dari orang tua langsung atau tidak langsung

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

Parent to Child (Transmisi eksplisit - Bisa berhasil)

Kasus 2. Variabel induk yang memegang objek induk (Tidak diperbolehkan)

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

Kasus 3. Variabel induk memegang objek anak (Selalu Berhasil)

Catatan: Karena objek memiliki sifat polimorfik, dimungkinkan untuk variabel dari tipe kelas induk untuk menampung tipe anak.

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

Kesimpulan: Setelah membaca di atas semua, semoga masuk akal sekarang seperti bagaimana konversi orang tua ke anak dimungkinkan (Kasus 3).

Jawab pertanyaan :

Jawaban Anda ada di kasus 2 Anda dapat melihat casting seperti itu tidak diizinkan oleh OOP dan Anda mencoba melanggar salah satu aturan dasar OOP. Jadi selalu pilih jalur yang aman.

Lebih jauh lagi, untuk menghindari situasi luar biasa seperti itu .net telah merekomendasikan penggunaan is / sebagai operator yang akan membantu Anda mengambil keputusan yang tepat dan menyediakan transmisi yang aman.


17

Ada beberapa kasus ketika pemeran seperti itu masuk akal.
Dalam kasus saya, saya menerima kelas BASE melalui jaringan, dan saya membutuhkan lebih banyak fitur untuk itu. Jadi memperolehnya untuk menanganinya di sisi saya dengan semua lonceng dan peluit yang saya inginkan, dan mentransmisikan kelas BASE yang diterima ke dalam kelas DERIVED bukanlah pilihan (Throws InvalidCastException of Course)

Salah satu solusi praktis yang dapat dipikirkan dengan baik adalah dengan mendeklarasikan kelas EXTENSION Helper yang sebenarnya TIDAK mewarisi kelas BASE, tetapi TERMASUK sebagai anggota.

public class BaseExtension
{
   Base baseInstance;

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

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

Jika Anda memiliki kopling longgar dan hanya membutuhkan beberapa fitur tambahan untuk kelas dasar tanpa BENAR - BENAR memiliki kebutuhan turunan yang mutlak, itu bisa menjadi solusi yang cepat dan sederhana.


Apakah saya benar dalam berpikir bahwa Anda mungkin ingin Anda di BaseExtensionsini setidaknya menerapkan IBasesedemikian rupa sehingga Anda dapat menggunakannya dalam konteks yang serupa? Atau tidak penting untuk kebutuhan Anda?
tobriand

beberapa kali termasuk dapat menjadi substitusi yang tepat untuk warisan
Vahid Ghadiri

13

Itu akan melanggar prinsip berorientasi objek. Saya akan mengatakan solusi elegan di sini dan di tempat lain dalam proyek ini menggunakan kerangka kerja pemetaan objek seperti AutoMapper untuk mengonfigurasi proyeksi.

Berikut adalah konfigurasi yang sedikit lebih kompleks daripada yang diperlukan tetapi cukup fleksibel untuk kebanyakan kasus:

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

Ketika aplikasi memulai panggilan AutoMapperConfiguration.Configure()dan kemudian Anda dapat memproyeksikan seperti ini:

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

Properti dipetakan menurut konvensi jadi jika kelas diwarisi, nama properti persis sama dan pemetaan dikonfigurasi secara otomatis. Anda dapat menambahkan properti tambahan dengan mengubah konfigurasi. Lihat dokumentasinya .


Menggunakan Automapper untuk memetakan tipe dengan satu properti ke properti lainnya (seperti yang dijelaskan OP) seperti menggunakan palu godam untuk memecahkan telur. Mengapa tidak memperbarui jenis turunan dan menetapkan propertinya sendiri (yaitu 1 baris kode).
bytedev

9

Paul, Anda tidak bertanya 'Bisakah saya melakukannya' - saya berasumsi Anda ingin tahu bagaimana melakukannya!

Kami harus melakukan ini pada sebuah proyek - ada banyak kelas yang kami siapkan secara umum hanya sekali, lalu menginisialisasi properti khusus untuk kelas turunan. Saya menggunakan VB jadi sampel saya dalam VB (noogies tangguh), tetapi saya mencuri sampel VB dari situs ini yang juga memiliki versi C # yang lebih baik:

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

Kode sampel:

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

Tentu saja ini bukan casting yang sebenarnya. Ini membuat objek turunan baru dan menyalin properti dari induk, membiarkan properti anak kosong. Hanya itu yang perlu saya lakukan dan sepertinya hanya itu yang perlu Anda lakukan. Perhatikan itu hanya menyalin properti, bukan anggota (variabel publik) di kelas (tetapi Anda dapat memperluasnya untuk melakukan itu jika Anda malu mengekspos anggota publik).

Casting secara umum membuat 2 variabel yang menunjuk ke objek yang sama (tutorial mini di sini, tolong jangan membuang pengecualian kasus sudut ke saya). Ada konsekuensi yang signifikan untuk ini (latihan untuk pembaca)!

Tentu saja saya harus mengatakan mengapa bahasa tidak membiarkan Anda pergi dari dasar ke contoh turunan, tetapi sebaliknya. bayangkan kasus di mana Anda dapat mengambil contoh dari kotak teks winforms (turunan) dan menyimpannya dalam variabel jenis kontrol Winforms. Tentu saja 'kontrol' dapat memindahkan objek sekitar OK dan Anda dapat menangani semua hal 'kontrol-y' tentang kotak teks (misalnya, atas, kiri, properti .text). Item khusus kotak teks (mis., .Multiline) tidak dapat dilihat tanpa mentransmisikan variabel jenis 'kontrol' yang menunjuk ke kotak teks di memori, tetapi masih ada di memori.

Sekarang bayangkan, Anda memiliki kontrol, dan Anda ingin memasukkan variabel jenis kotak teks ke dalamnya. Kontrol dalam memori kehilangan 'multiline' dan hal-hal textboxy lainnya. Jika Anda mencoba mereferensikannya, kontrol tidak akan secara ajaib menumbuhkan properti multiline! Properti (lihat seperti variabel anggota di sini, yang sebenarnya menyimpan nilai - karena ada di dalam memori instance kotak teks) harus ada. Karena Anda sedang melakukan casting, ingat, itu harus menjadi objek yang sama dengan yang Anda tunjuk. Oleh karena itu, ini bukanlah batasan bahasa, secara filosofis tidak mungkin terjadi dengan cara seperti itu.


1
Saya tahu ini jauh setelah fakta, tetapi Anda harus menyertakan "AndAlso dstProperty.CanWrite" ke tes "If dstProperty IsNot Nothing", untuk memastikan itu bukan properti hanya-baca.
JamesMLV

@JamesMLV - terima kasih tangkapan yang bagus. 'setelah fakta' - sepertinya OP tidak akan menerima jawaban apa pun :-( jadi tidak ada fakta yang akan menyusul. Oh well.
FastAl

4

Instance objek harus dibuat menggunakan tipe anak kelas, Anda tidak bisa mentransmisikan instance tipe induk ke tipe anak


2

Bagi saya, cukup menyalin semua bidang properti dari kelas dasar ke induk seperti ini:

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

Solusi universal untuk objek apa pun dapat ditemukan di sini


2

Mulai C # 7.0, Anda dapat menggunakan kata kunci is untuk melakukan ini:

Dengan kelas tersebut ditentukan:

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

Anda kemudian dapat melakukan sesuatu seperti:

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

Yang mana setara dengan:

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

Sekarang Anda dapat menghubungi:

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

Sebaik

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

Dengan begitu Anda bisa yakin bahwa downcast Anda akan valid dan tidak akan membuat pengecualian!


1

Untuk mentransmisikan, objek sebenarnya harus dari Jenis yang sama dengan atau berasal dari Jenis yang Anda coba untuk melemparkan ke ...

atau, untuk menyatakannya dengan cara yang berlawanan, Jenis yang Anda coba transmisikan harus sama dengan, atau kelas dasar, jenis objek yang sebenarnya.

jika objek Anda yang sebenarnya adalah tipe Baseclass , maka Anda tidak dapat mentransmisikannya ke kelas turunan Type ...


1

Variasi pada pendekatan serialisasi untuk mereka yang menggunakan ServiceStack:

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

atau lebih bertele-tele:

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

Serialisasi ServiceStack mungkin super cepat, tetapi jelas, ini bukan solusi untuk konversi besar-besaran dalam transfer latensi rendah, atau untuk jenis yang sangat kompleks. Itu mungkin jelas bagi siapa pun yang menggunakan ServiceStack, tetapi saya pikir saya akan mengklarifikasi untuk mengantisipasi komentar.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.