Mengapa kelas XML-Serializable memerlukan konstruktor tanpa parameter

173

Saya menulis kode untuk melakukan serialisasi Xml. Dengan fungsi di bawah ini.

public static string SerializeToXml(object obj)
{
    XmlSerializer serializer = new XmlSerializer(obj.GetType());
    using (StringWriter writer = new StringWriter())
    {
        serializer.Serialize(writer, obj);
        return writer.ToString();
    }
}

Jika argumen adalah turunan dari kelas tanpa konstruktor tanpa parameter, ia akan melempar pengecualian.

Pengecualian Tidak Tertangani: System.InvalidOperationException: CSharpConsole.Foo tidak dapat diserialisasi karena tidak memiliki konstruktor tanpa parameter. di System.Xml.Serialization.TypeDesc.CheckSupported () di System.Xml.Serialization.TypeScope.GetTypeDesc (jenis Type, MemberInfo sumber Referensi langsung Boolean) di System.Xml.Serialization.XmlReflectionImporter.ImportTypeMapping (tipe Type, XmlRootAttribute root, String defaultNamespace) di System.Xml.Serialization.XmlSerializer..ctor (tipe type, String defaultName space) di System.Xml.Serationization. XmlSerializer..ctor (Jenis tipe)

Mengapa harus ada konstruktor tanpa parameter untuk memungkinkan serialisasi xml berhasil?

EDIT: terima kasih atas jawaban cfeduke. Konstruktor tanpa parameter dapat bersifat pribadi atau internal.

Morgan Cheng
sumber
1
Jika Anda tertarik, saya menemukan cara membuat objek tanpa memerlukan konstruktor (lihat pembaruan) - tetapi ini tidak akan membantu XmlSerializer sama sekali - itu masih menuntutnya. Berguna untuk kode khusus, mungkin.
Marc Gravell
1
XmlSerializermembutuhkan konstruktor tanpa parameter default untuk deserialisasi.
Amit Kumar Ghosh

Jawaban:

243

Selama de-serialisasi objek, kelas yang bertanggung jawab untuk de-serialisasi objek menciptakan turunan dari kelas serial dan kemudian melanjutkan untuk mengisi bidang dan properti serial hanya setelah memperoleh sebuah instance untuk diisi.

Anda dapat membuat konstruktor privateatau internaljika Anda mau, asalkan tanpa parameter.

cfeduke
sumber
1
Oh, jadi, saya bisa membuat parameterless ctor privat atau internal dan serialisasi masih berfungsi. Terima kasih atas jawaban anda.
Morgan Cheng
2
Ya saya sering melakukannya, meskipun saya telah menerima bahwa konstruktor tanpa parameter publik bagus karena mereka memungkinkan Anda untuk menggunakan "baru ()" dengan generik dan sintaks inisialisasi baru. Untuk konstruktor berparameter menggunakan metode pabrik statis atau implementasi pola pembangun.
cfeduke
14
Tip aksesibilitas bagus, tetapi penjelasan Anda tidak masuk akal untuk serialisasi. Suatu objek perlu dibuat hanya untuk de-serialisasi. Saya akan menebak bahwa jenis-periksa kode dibangun ke konstruktor XmlSerializer karena sebuah instance dapat digunakan dua arah.
Tomer Gabel
7
@ jwg Salah satu contoh adalah ketika Anda mengirim XML ke layanan web dan tidak tertarik menerima objek-objek itu di komponen Anda sendiri.
Tomer Gabel
5
Ingatlah bahwa meskipun Anda membuat konstruktor tanpa parameter privateatau internal, semua properti Anda yang nilainya diserialisasi harus memiliki publicsetter.
chrnola
75

Ini adalah batasan XmlSerializer. Catat itu BinaryFormatterdan DataContractSerializer jangan memerlukan ini - mereka dapat membuat objek yang tidak diinisialisasi dari eter dan menginisialisasi itu selama deserialisasi.

Karena Anda menggunakan xml, Anda dapat mempertimbangkan menggunakan DataContractSerializerdan menandai kelas Anda dengan [DataContract]/ [DataMember], tetapi perhatikan bahwa ini mengubah skema (misalnya, tidak ada yang setara dengan[XmlAttribute] - semuanya menjadi elemen).

Pembaruan: jika Anda benar-benar ingin tahu, BinaryFormatterdan lain-lain gunakan FormatterServices.GetUninitializedObject()untuk membuat objek tanpa memanggil konstruktor. Mungkin berbahaya; Saya tidak menyarankan terlalu sering menggunakannya ;-p Lihat juga komentar di MSDN:

Karena instance baru objek diinisialisasi ke nol dan tidak ada konstruktor dijalankan, objek mungkin tidak mewakili keadaan yang dianggap valid oleh objek itu. Metode saat ini hanya boleh digunakan untuk deserialisasi ketika pengguna bermaksud untuk segera mengisi semua bidang. Itu tidak membuat string yang tidak diinisialisasi, karena membuat instance kosong dari tipe yang tidak dapat diubah tidak ada gunanya.

Saya memiliki mesin serialisasi sendiri , tetapi saya tidak bermaksud menggunakannya FormatterServices; Saya sangat suka mengetahui bahwa seorang konstruktor ( konstruktor apa saja ) sebenarnya telah dieksekusi.

Marc Gravell
sumber
Terima kasih atas tipnya tentang FormatterServices.GetUninitializedObject (Type). :)
Omer van Kloeten
6
Heh; ternyata saya tidak mengikuti saran saya sendiri; protobuf-net telah (opsional) mengizinkan FormatterServicespenggunaan untuk usia
Marc Gravell
1
Tapi yang saya tidak mengerti adalah, dalam hal tidak ada konstruktor yang ditentukan, kompiler membuat konstruktor tanpa parameter publik. Jadi mengapa itu tidak cukup baik untuk mesin deserialisasi xml?
toddmo
Jika saya ingin menghapus XML dan menginisialisasi objek tertentu menggunakan konstruktor mereka (sehingga elemen / atribut disediakan melalui konstruktor), apakah ada cara APA SAJA untuk mencapai ini? Apakah tidak ada cara untuk menyesuaikan proses serialisasi sehingga membangun objek menggunakan konstruktor mereka?
Shimmy Weitzhandler
1
@ Kimmy tidak; itu tidak didukung. Ada adalah IXmlSerializable , tapi: itu terjadi setelah konstruktor, dan b: sangat jelek dan sulit untuk mendapatkan hak (terutama deserialization) - saya sangat menyarankan agar mencoba untuk menerapkan itu, tapi: tidak akan memungkinkan Anda untuk menggunakan konstruktor
Marc Gravell
4

Jawabannya adalah: tanpa alasan apa pun.

Bertentangan dengan namanya, XmlSerializerkelas ini digunakan tidak hanya untuk serialisasi, tetapi juga untuk deserialisasi. Ia melakukan pemeriksaan tertentu di kelas Anda untuk memastikan bahwa itu akan berfungsi, dan beberapa cek itu hanya berkaitan dengan deserialisasi, tetapi tetap melakukan semuanya, karena tidak tahu apa yang ingin Anda lakukan nanti.

Cek yang gagal lulus oleh kelas Anda adalah salah satu cek yang hanya berkaitan dengan deserialisasi. Inilah yang terjadi:

  • Selama deserialisasi, XmlSerializerkelas perlu membuat instance tipe Anda.

  • Untuk membuat turunan dari suatu tipe, konstruktor dari tipe itu perlu dipanggil.

  • Jika Anda tidak mendeklarasikan konstruktor, kompilator telah menyediakan konstruktor tanpa parameter default, tetapi jika Anda mendeklarasikan konstruktor, maka itulah satu-satunya konstruktor yang tersedia.

  • Jadi, jika konstruktor yang Anda deklarasikan menerima parameter, maka satu-satunya cara untuk membuat instance kelas Anda adalah dengan memanggil konstruktor yang menerima parameter.

  • Namun, XmlSerializertidak mampu memanggil konstruktor apa pun kecuali konstruktor tanpa parameter, karena tidak tahu parameter apa yang dilewatkan ke konstruktor yang menerima parameter. Jadi, ini memeriksa untuk melihat apakah kelas Anda memiliki konstruktor tanpa parameter, dan karena tidak, itu gagal.

Jadi, jika XmlSerializerkelas telah ditulis sedemikian rupa untuk hanya melakukan pemeriksaan yang berkaitan dengan serialisasi, maka kelas Anda akan lulus, karena sama sekali tidak ada tentang serialisasi yang membuatnya perlu untuk memiliki konstruktor tanpa parameter.

Seperti yang telah ditunjukkan orang lain, solusi cepat untuk masalah Anda adalah dengan menambahkan konstruktor tanpa parameter. Sayangnya, ini juga merupakan solusi kotor, karena itu berarti Anda tidak dapat memiliki readonlyanggota yang diinisialisasi dari parameter konstruktor.

Selain semua ini, XmlSerializerkelas dapat ditulis sedemikian rupa untuk memungkinkan deserialisasi kelas tanpa konstruktor tanpa parameter. Yang diperlukan hanyalah memanfaatkan "Pola Desain Metode Pabrik" (Wikipedia) . Dari kelihatannya, Microsoft memutuskan bahwa pola desain ini terlalu maju untuk programmer DotNet, yang tampaknya tidak perlu bingung dengan hal-hal seperti itu. Jadi, programmer DotNet harus lebih baik tetap menggunakan konstruktor tanpa parameter, menurut Microsoft.

Mike Nakis
sumber
Lol Anda katakan, For no good reason whatsoever,lalu lanjutkan dengan mengatakan, XmlSerializer is not capable of invoking any constructor except a parameterless constructor, because it does not know what parameters to pass to constructors that accept parameters.Jika tidak tahu parameter apa yang harus dilewatkan ke konstruktor, lalu bagaimana ia tahu parameter apa yang dilewatkan ke pabrik? Atau pabrik mana yang digunakan? Saya tidak bisa membayangkan alat ini lebih mudah digunakan - Anda ingin kelas deserialized, lalu biarkan deserializer membuat contoh default dan kemudian mengisi setiap bidang yang Anda tandai. Mudah.
Chuck
0

Pertama-tama, inilah yang tertulis dalam dokumentasi . Saya pikir ini adalah salah satu bidang kelas Anda, bukan yang utama - dan bagaimana Anda ingin deserialiser membangunnya kembali tanpa konstruksi parameterless?

Saya pikir ada solusi untuk menjadikan konstruktor sebagai pribadi.

Dmitry Khalatov
sumber