ทำไมคลาส XML-Serializable จึงต้องมี Constructor แบบไม่มีพารามิเตอร์


173

ฉันกำลังเขียนโค้ดเพื่อทำซีเรียลไลซ์เซชั่นของ XML ด้วยฟังก์ชั่นด้านล่าง

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

ถ้าอาร์กิวเมนต์เป็นอินสแตนซ์ของคลาสที่ไม่มีตัวสร้างแบบไม่มีพารามิเตอร์จะมีข้อยกเว้น

Unhandled Exception: System.InvalidOperationException: CSharpConsole.Foo ไม่สามารถทำให้เป็นอนุกรมได้เนื่องจากไม่มี Constructor แบบไม่มีพารามิเตอร์ ที่ System.Xml.Serialization.TypeDesc.CheckSupported () ที่ System.Xml.Serialization.TypeScope.GetTypeDesc (ประเภทประเภท MemberInfo sourc e, บูลีน directReference, บูลีน throwOnError) ที่ System.Xml.Serialization.TypeDesc.CheckSupported () ที่ System.Xml.Serialization.TypeDesc.CheckSupported () การอ้างอิงบูลีนโดยตรง) ที่ System.Xml.Serialization.XmlReflectionImporter.ImportTypeMapping (ประเภทประเภทราก XmlRootAttribute, สตริง defaultNamespace) ที่ System.Xml.Serialization.XmlSerializer .. ประเภทชนิดพื้นที่สตริง defaultName ที่ System.Xml.Serializer XmlSerializer..ctor (ประเภทประเภท)

ทำไมต้องมีคอนสตรัคเตอร์แบบไม่มีพารามิเตอร์เพื่อให้ xml เป็นอนุกรมสำเร็จ

แก้ไข: ขอบคุณสำหรับคำตอบของ cfeduke ตัวสร้างแบบไม่มีพารามิเตอร์สามารถเป็นแบบส่วนตัวหรือแบบภายใน


1
หากคุณสนใจฉันพบวิธีการสร้างวัตถุโดยไม่ต้องใช้ตัวสร้าง (ดูอัปเดต) - แต่นี่จะไม่ช่วย XmlSerializer เลย - มันยังต้องการมันอยู่ มีประโยชน์สำหรับรหัสที่กำหนดเองอาจจะ
Marc Gravell

1
XmlSerializerต้องการตัวสร้างแบบไม่มีพารามิเตอร์เริ่มต้นสำหรับการดีซีเรียลไลซ์เซชัน
Amit Kumar Ghosh

คำตอบ:


243

ในระหว่างการ de-serialization ของวัตถุคลาสที่รับผิดชอบในการ de-serializing วัตถุสร้างอินสแตนซ์ของคลาสที่ serialized แล้วดำเนินการเพื่อเติมฟิลด์ที่เป็นอนุกรม

คุณสามารถสร้างคอนสตรัคprivateหรือinternalถ้าคุณต้องการตราบใดที่มันไร้พารามิเตอร์


1
โอ้ดังนั้นฉันสามารถทำให้ ctor แบบไม่มีพารามิเตอร์เป็นส่วนตัวหรือภายในและการทำให้เป็นอนุกรมยังคงใช้งานได้ ขอบคุณสำหรับคำตอบ.
มอร์แกนเฉิง

2
ใช่ฉันทำบ่อยครั้งแม้ว่าฉันจะยอมรับว่าตัวสร้างแบบไม่มีพารามิเตอร์สาธารณะนั้นยอดเยี่ยมเพราะพวกเขาอนุญาตให้คุณใช้ "new ()" กับ generics และไวยากรณ์การกำหนดค่าเริ่มต้นใหม่ได้ สำหรับคอนสตรัคเตอร์พารามิเตอร์ใช้วิธีการคงที่จากโรงงาน
cfeduke

14
เคล็ดลับการช่วยสำหรับการเข้าถึงเป็นสิ่งที่ดี แต่คำอธิบายของคุณไม่สมเหตุสมผลสำหรับการทำให้เป็นอนุกรม วัตถุจะต้องสร้างขึ้นสำหรับการทำให้เป็นอนุกรมเท่านั้น ฉันสงสัยว่ารหัสตรวจสอบชนิดถูกสร้างไว้ในตัวสร้าง XmlSerializer เนื่องจากอินสแตนซ์เดียวสามารถใช้ได้ทั้งสองวิธี
Tomer Gabel

7
@jwg ตัวอย่างหนึ่งคือเมื่อคุณส่ง XML ของคุณไปยังบริการเว็บบางประเภทและไม่สนใจรับวัตถุเหล่านั้นในองค์ประกอบของคุณเอง
Tomer Gabel

5
โปรดทราบว่าแม้ว่าคุณจะกำหนดพารามิเตอร์แบบไม่มีพารามิเตอร์privateหรือinternalคุณสมบัติทั้งหมดของคุณที่มีค่าเป็นอนุกรมต้องมีตัวpublicตั้งค่า
chrnola

75

นี่เป็นข้อ จำกัด XmlSerializerของ โปรดทราบว่าBinaryFormatterและDataContractSerializer ไม่ต้องการสิ่งนี้ - พวกเขาสามารถสร้างวัตถุที่ไม่มีการกำหนดค่าเริ่มต้นจากอีเธอร์และเริ่มต้นได้ในระหว่างการดีซีเรียลไลเซชัน

เนื่องจากคุณใช้ xml คุณอาจพิจารณาใช้DataContractSerializerและทำเครื่องหมายชั้นเรียนของคุณด้วย[DataContract]/ [DataMember] แต่โปรดทราบว่าสิ่งนี้จะเปลี่ยนสคีมา (ตัวอย่างเช่นไม่มีการเทียบเท่า[XmlAttribute]- ทุกอย่างกลายเป็นองค์ประกอบ)

อัปเดต: หากคุณต้องการทราบจริงๆBinaryFormatterและใช้FormatterServices.GetUninitializedObject()เพื่อสร้างวัตถุโดยไม่ต้องเรียกใช้ตัวสร้าง อาจเป็นอันตรายได้; ฉันไม่แนะนำให้ใช้บ่อยเกินไป ;-p ดูหมายเหตุใน MSDN:

เนื่องจากอินสแตนซ์ใหม่ของวัตถุนั้นถูกเตรียมใช้งานเป็นศูนย์และไม่มีการเรียกใช้งานตัวสร้างวัตถุอาจไม่แสดงสถานะที่ถือว่าถูกต้องโดยวัตถุนั้น เมธอดปัจจุบันควรใช้สำหรับการดีซีเรียลไลซ์เซชันเมื่อผู้ใช้ต้องการเติมฟิลด์ทั้งหมดทันที มันไม่ได้สร้างสตริงที่ไม่มีค่าเริ่มต้นเนื่องจากการสร้างอินสแตนซ์ที่ว่างเปล่าของประเภทที่ไม่เปลี่ยนรูปไม่ได้ให้บริการไม่มีวัตถุประสงค์

ฉันมีของฉันเองเครื่องยนต์อนุกรม แต่ฉันไม่ได้ตั้งใจทำให้มันใช้FormatterServices; ฉันชอบที่รู้ว่าตัวสร้าง (ตัวสร้างใด ๆ ) ได้ดำเนินการจริง


ขอบคุณสำหรับเคล็ดลับเกี่ยวกับ FormatterServices.GetUninitializedObject (Type) :)
Omer van Kloeten

6
หึ; ปรากฎว่าฉันไม่ปฏิบัติตามคำแนะนำของตัวเอง; protobuf-net ได้อนุญาตFormatterServicesการใช้งานสำหรับทุกเพศทุกวัย
Marc Gravell

1
แต่สิ่งที่ฉันไม่เข้าใจคือในกรณีที่ไม่ได้ระบุตัวสร้างคอมไพเลอร์จะสร้างตัวสร้างพารามิเตอร์แบบไม่มีพารามิเตอร์สาธารณะ เหตุใดจึงไม่ดีพอสำหรับเครื่องมือดีซีเรียลไลเซชั่นของ XML
toddmo

ถ้าฉันต้องการ deserialize XML และเริ่มต้นวัตถุบางอย่างโดยใช้ตัวสร้างของพวกเขา (เพื่อให้องค์ประกอบ / คุณลักษณะที่มีให้ผ่านตัวสร้าง) มีวิธีใดที่จะบรรลุสิ่งนี้? ไม่มีวิธีในการกำหนดกระบวนการทำให้เป็นอนุกรมเพื่อสร้างวัตถุโดยใช้ Constructor ของพวกเขาหรือไม่
Shimmy Weitzhandler

1
@ Shimmy nope; ไม่รองรับ มีเป็น IXmlSerializableแต่: ที่เกิดขึ้นหลังจากการสร้างและ b: มันเป็นอย่างน่าเกลียดและยากที่จะได้รับสิทธิ (โดยเฉพาะ deserialization) - ผมขอแนะนำให้พยายามที่จะใช้นั้น แต่มันจะไม่ยอมให้คุณใช้ก่อสร้าง
Marc Gravell

4

คำตอบคือ: ไม่มีเหตุผลที่ดีใด ๆ

ตรงกันข้ามกับชื่อXmlSerializerคลาสใช้ไม่เพียง แต่สำหรับซีเรียลไลซ์เซชัน แต่ยังใช้สำหรับการดีซีเรียลไลเซชัน มันทำการตรวจสอบบางอย่างในชั้นเรียนของคุณเพื่อให้แน่ใจว่าจะใช้งานได้และการตรวจสอบบางอย่างนั้นเกี่ยวข้องเฉพาะกับการดีซีเรียลไลเซชั่นเท่านั้น

การตรวจสอบว่าคลาสของคุณไม่ผ่านเป็นหนึ่งในการตรวจสอบที่เกี่ยวข้องกับการดีซีเรียลไลเซชันเท่านั้น นี่คือสิ่งที่เกิดขึ้น:

  • ในระหว่างการดีซีเรียลไลเซชันXmlSerializerคลาสจะต้องสร้างอินสแตนซ์ของประเภทของคุณ

  • เพื่อที่จะสร้างอินสแตนซ์ของประเภทคอนสตรัคเตอร์ของประเภทนั้นจำเป็นต้องถูกเรียกใช้

  • หากคุณไม่ได้ประกาศตัวสร้างคอมไพเลอร์ได้กำหนดตัวสร้างพารามิเตอร์แบบไม่มีค่าเริ่มต้นแล้ว แต่ถ้าคุณประกาศตัวสร้างแล้วนั่นเป็นเพียงตัวสร้างที่มีอยู่เท่านั้น

  • ดังนั้นถ้า Constructor ที่คุณประกาศยอมรับพารามิเตอร์ดังนั้นวิธีเดียวที่จะทำให้คลาสของคุณเป็นอินสแตนซ์โดยการเรียก Constructor ที่ยอมรับพารามิเตอร์

  • อย่างไรก็ตามXmlSerializerไม่สามารถเรียกใช้ตัวสร้างใด ๆ ได้ยกเว้นตัวสร้างแบบไม่มีพารามิเตอร์เนื่องจากไม่ทราบว่าพารามิเตอร์ใดที่จะส่งผ่านไปยังตัวสร้างที่ยอมรับพารามิเตอร์ ดังนั้นมันจะตรวจสอบเพื่อดูว่าคลาสของคุณมีคอนสตรัคเตอร์แบบไม่มีพารามิเตอร์หรือไม่และมันไม่ล้มเหลว

ดังนั้นหาก XmlSerializerคลาสนั้นถูกเขียนในลักษณะที่จะทำการตรวจสอบที่เกี่ยวข้องกับการทำให้เป็นอนุกรมเท่านั้นคลาสของคุณจะผ่านเพราะไม่มีอะไรเลยเกี่ยวกับการทำให้เป็นอนุกรมที่ทำให้จำเป็นต้องมีคอนสตรัคเตอร์แบบไม่มีพารามิเตอร์

ดังที่คนอื่น ๆ ชี้ไปแล้ววิธีแก้ปัญหาอย่างรวดเร็วของคุณก็คือการเพิ่มคอนสตรัคเตอร์แบบไม่มีพารามิเตอร์ น่าเสียดายที่มันเป็นโซลูชันที่สกปรกด้วยเพราะนั่นหมายความว่าคุณไม่สามารถให้readonlyสมาชิกเริ่มต้นจากพารามิเตอร์คอนสตรัคเตอร์ได้

นอกเหนือจากทั้งหมดนี้XmlSerializerคลาสอาจถูกเขียนในลักษณะที่จะยอมให้มีการดีซีเรียลไลซ์เซชั่นของคลาสโดยไม่มีตัวสร้างแบบไม่มีพารามิเตอร์ ทั้งหมดก็จะใช้เวลาที่จะทำให้การใช้งานของ"โรงงานรูปแบบวิธีการออกแบบ" (วิกิพีเดีย) จากรูปลักษณ์ของมัน Microsoft ตัดสินใจว่ารูปแบบการออกแบบนี้ล้ำหน้าเกินไปสำหรับโปรแกรมเมอร์ DotNet ซึ่งเห็นได้ชัดว่าไม่ควรสับสนกับสิ่งเหล่านี้โดยไม่จำเป็น ดังนั้นโปรแกรมเมอร์ DotNet ควรยึดติดกับคอนสตรัคเตอร์ที่ไม่มีพารามิเตอร์ตามที่ดีกว่า


ฮ่า ๆ ๆ คุณพูดFor no good reason whatsoever,แล้วไปพูด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.ถ้าไม่ทราบว่าพารามิเตอร์ที่จะส่งไปยังตัวสร้างแล้วมันจะรู้พารามิเตอร์ที่จะส่งไปยังโรงงานได้อย่างไร หรือโรงงานที่จะใช้? ฉันนึกภาพไม่ออกว่าเครื่องมือนี้ใช้งานได้ตรงไปตรงมามากกว่านี้ - คุณต้องการคลาสที่ดีซีเรียลไลซ์แล้วให้ deserializer สร้างอินสแตนซ์เริ่มต้นแล้วเติมแต่ละฟิลด์ที่คุณแท็ก ง่าย.
Chuck

0

แรกของทุกสิ่งที่เขียนไว้ในเอกสาร ฉันคิดว่ามันเป็นหนึ่งในสาขาวิชาของคุณไม่ใช่สาขาหลัก - และคุณต้องการให้ deserialiser สร้างมันกลับโดยไม่มีการสร้างแบบไม่มีพารามิเตอร์

ฉันคิดว่ามีวิธีแก้ปัญหาเพื่อให้คอนสตรัคเตอร์เป็นแบบส่วนตัว

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.