XML Serileştirme ve Devralınan Türler


85

Benim itibaren ardından önceki soruya ben XML serialize benim nesne modelini alma konusunda çalışıyoruz. Ama şimdi bir sorunla karşılaştım (quelle sürpriz!).

Sahip olduğum sorun, somut türetilmiş türlerle doldurulan soyut temel sınıf türünde bir koleksiyonum olması.

Katılan tüm sınıflara XML özniteliklerini eklemenin iyi olacağını ve her şeyin şeftali gibi olacağını düşündüm. Ne yazık ki, durum bu değil!

Bu yüzden Google'da biraz araştırma yaptım ve neden işe yaramadığını şimdi anlıyorum . Bunda aslında serialize için bazı akıllı yansıma yapıyor soyut türüne göre, bunun konuşuyor ne cehennem bilemiyorum için / XML ve onun beri nesneleri . İnce.XmlSerializer

CodeProject'te bu sayfaya rastladım , bu çok yardımcı olabilir gibi görünüyor (henüz tam olarak okumak / tüketmek için), ancak bu sorunu StackOverflow tablosuna da getirmek isteyip istemediğinizi görmek için düşündüm. Bunu mümkün olan en hızlı / en hafif şekilde çalıştırmak ve çalıştırmak için hacks / tricks.

Eklemem gereken bir şey de , rotadan aşağı inmek İSTEMİYORUMXmlInclude . Onunla çok fazla bağlantı var ve sistemin bu alanı yoğun bir gelişme altında, bu yüzden gerçek bir bakım sorunu olacak!


1
Serileştirmeye çalıştığınız sınıflardan çıkarılan bazı ilgili kod parçacıklarını görmek faydalı olacaktır.
Rex M


Bu ileti dizisinde çok uzun süredir hiçbir şey olmadığı için kafanız biraz karıştı mı?
Rob Cooper

Yanıtlar:


54

Sorun çözüldü!

Tamam, sonunda oraya geldim (kuşkusuz buradan çok yardım alarak !).

Öyleyse özetleyin:

Hedefler:

  • Bakım baş ağrısından dolayı XmlInclude rotasına girmek istemedim .
  • Bir çözüm bulunduğunda, diğer uygulamalarda uygulanmasının hızlı olmasını istedim.
  • Özet türlerinin koleksiyonları ve ayrıca bireysel soyut özellikler kullanılabilir.
  • Somut derslerde "özel" şeyler yapmak zorunda kalmayı gerçekten istemedim.

Belirlenen Sorunlar / Dikkat Edilmesi Gereken Noktalar:

  • XmlSerializer oldukça güzel bir yansıtma yapar, ancak soyut türler söz konusu olduğunda çok sınırlıdır (yani alt sınıflarla değil, yalnızca soyut türün örnekleriyle çalışacaktır).
  • Xml öznitelik çözümleyicileri, XmlSerializer'ın bulduğu özellikleri nasıl ele aldığını tanımlar. Fiziksel tür de belirtilebilir, ancak bu , sınıf ile serileştirici arasında sıkı bir bağlantı oluşturur (iyi değil).
  • IXmlSerializable uygulayan bir sınıf oluşturarak kendi XmlSerializer'ımızı uygulayabiliriz .

Çözüm

Çalışacağınız soyut tür olarak genel türü belirttiğiniz genel bir sınıf oluşturdum. Bu, sınıfa soyut tür ile somut tür arasında "çevirme" yeteneği verir, çünkü dökümü sabit kodlayabiliriz (yani XmlSerializer'ın alabileceğinden daha fazla bilgi alabiliriz).

Daha sonra IXmlSerializable arabirimini uyguladım , bu oldukça basittir, ancak serileştirirken somut sınıfın türünü XML'e yazdığımızdan emin olmamız gerekir, böylece serileştirmeyi kaldırırken onu geri döndürebiliriz. Ayrıca, iki sınıfın büyük olasılıkla farklı olduğu meclislerin tamamen nitelendirilmesi gerektiğine dikkat etmek önemlidir . Elbette küçük bir tür kontrolü ve burada olması gereken şeyler var.

XmlSerializer yayınlayamadığından, bunu yapmak için kodu sağlamamız gerekir, böylece örtük operatör aşırı yüklenir (bunu yapabileceğinizi bile bilmiyordum!).

AbstractXmlSerializer için kod şudur:

using System;
using System.Collections.Generic;
using System.Text;
using System.Xml.Serialization;

namespace Utility.Xml
{
    public class AbstractXmlSerializer<AbstractType> : IXmlSerializable
    {
        // Override the Implicit Conversions Since the XmlSerializer
        // Casts to/from the required types implicitly.
        public static implicit operator AbstractType(AbstractXmlSerializer<AbstractType> o)
        {
            return o.Data;
        }

        public static implicit operator AbstractXmlSerializer<AbstractType>(AbstractType o)
        {
            return o == null ? null : new AbstractXmlSerializer<AbstractType>(o);
        }

        private AbstractType _data;
        /// <summary>
        /// [Concrete] Data to be stored/is stored as XML.
        /// </summary>
        public AbstractType Data
        {
            get { return _data; }
            set { _data = value; }
        }

        /// <summary>
        /// **DO NOT USE** This is only added to enable XML Serialization.
        /// </summary>
        /// <remarks>DO NOT USE THIS CONSTRUCTOR</remarks>
        public AbstractXmlSerializer()
        {
            // Default Ctor (Required for Xml Serialization - DO NOT USE)
        }

        /// <summary>
        /// Initialises the Serializer to work with the given data.
        /// </summary>
        /// <param name="data">Concrete Object of the AbstractType Specified.</param>
        public AbstractXmlSerializer(AbstractType data)
        {
            _data = data;
        }

        #region IXmlSerializable Members

        public System.Xml.Schema.XmlSchema GetSchema()
        {
            return null; // this is fine as schema is unknown.
        }

        public void ReadXml(System.Xml.XmlReader reader)
        {
            // Cast the Data back from the Abstract Type.
            string typeAttrib = reader.GetAttribute("type");

            // Ensure the Type was Specified
            if (typeAttrib == null)
                throw new ArgumentNullException("Unable to Read Xml Data for Abstract Type '" + typeof(AbstractType).Name +
                    "' because no 'type' attribute was specified in the XML.");

            Type type = Type.GetType(typeAttrib);

            // Check the Type is Found.
            if (type == null)
                throw new InvalidCastException("Unable to Read Xml Data for Abstract Type '" + typeof(AbstractType).Name +
                    "' because the type specified in the XML was not found.");

            // Check the Type is a Subclass of the AbstractType.
            if (!type.IsSubclassOf(typeof(AbstractType)))
                throw new InvalidCastException("Unable to Read Xml Data for Abstract Type '" + typeof(AbstractType).Name +
                    "' because the Type specified in the XML differs ('" + type.Name + "').");

            // Read the Data, Deserializing based on the (now known) concrete type.
            reader.ReadStartElement();
            this.Data = (AbstractType)new
                XmlSerializer(type).Deserialize(reader);
            reader.ReadEndElement();
        }

        public void WriteXml(System.Xml.XmlWriter writer)
        {
            // Write the Type Name to the XML Element as an Attrib and Serialize
            Type type = _data.GetType();

            // BugFix: Assembly must be FQN since Types can/are external to current.
            writer.WriteAttributeString("type", type.AssemblyQualifiedName);
            new XmlSerializer(type).Serialize(writer, _data);
        }

        #endregion
    }
}

Öyleyse, oradan, XmlSerializer'a varsayılan yerine serileştiricimizle çalışmasını nasıl söyleyebiliriz? Türümüzü Xml öznitelikleri türü özelliği içinde geçirmeliyiz, örneğin:

[XmlRoot("ClassWithAbstractCollection")]
public class ClassWithAbstractCollection
{
    private List<AbstractType> _list;
    [XmlArray("ListItems")]
    [XmlArrayItem("ListItem", Type = typeof(AbstractXmlSerializer<AbstractType>))]
    public List<AbstractType> List
    {
        get { return _list; }
        set { _list = value; }
    }

    private AbstractType _prop;
    [XmlElement("MyProperty", Type=typeof(AbstractXmlSerializer<AbstractType>))]
    public AbstractType MyProperty
    {
        get { return _prop; }
        set { _prop = value; }
    }

    public ClassWithAbstractCollection()
    {
        _list = new List<AbstractType>();
    }
}

Burada, bir koleksiyonumuz ve açığa çıkan tek bir özelliğimiz olduğunu görebilirsiniz ve tek yapmamız gereken, tür adındaki parametreyi Xml bildirimine eklemektir , kolay! : D

NOT: Bu kodu kullanırsanız, gerçekten duymaktan memnun olurum. Aynı zamanda topluluğa daha fazla insan çekmeye yardımcı olacaktır :)

Şimdi, ama hepsinin profesyonelleri ve eksileri olduğu için buradaki cevaplarla ne yapılacağından emin değil. Yararlı olduğunu düşündüğümleri modifiye edeceğim (olmayanlara gücenme) ve temsilciye sahip olduğumda bunu kapatacağım :)

İlginç bir sorun ve çözmek için eğlenceli! :)


Bu problemle bir süre önce kendim karşılaştım. Kişisel olarak, XmlSerializer'ı terk edip doğrudan IXmlSerializable arayüzünü kullanmayı bıraktım, çünkü tüm sınıflarımın zaten onu uygulaması gerekiyordu. Aksi takdirde, çözümler oldukça benzerdir. Yine de iyi yazım :)
Thorarin

Listeyi Dizilere dönüştürdüğümüz XML_ özelliklerini kullanıyoruz :)
Arcturus

2
Çünkü sınıfı dinamik olarak başlatmak için parametresiz bir kurucuya ihtiyaç vardır.
Silas Hansen

1
Merhaba! Bir süredir bunun gibi bir çözüm arıyorum. Bence harika! Nasıl kullanacağımı çözemesem de, bir örnek verebilir misiniz? Nesnelerinizi içeren sınıfınızı veya listeyi mi serileştiriyorsunuz?
Daniel

1
Güzel kod. Parametresiz yapıcının bildirilebileceğini privateveya protecteddiğer sınıflar tarafından kullanılamayacağını zorlayabileceğini unutmayın.
tcovo

9

Bakılması gereken bir şey, XmlSerialiser yapıcısında, serileştiricinin çözmede zorluk yaşayabileceği bir dizi türü geçirebilmenizdir. Bir koleksiyonun veya karmaşık veri yapılarının serileştirilmesi gerektiğinde ve bu türlerin farklı derlemelerde yaşadığı birkaç kez bunu kullanmak zorunda kaldım.

ExtraTypes parametresine sahip XmlSerialiser Oluşturucu

DÜZENLEME: Bu yaklaşımın XmlInclude özniteliklerine vb. Göre faydası olduğunu ekleyeceğim, çalışma zamanında olası somut türlerinizin bir listesini keşfetmenin ve derlemenin bir yolunu bulabilir ve bunları doldurabilirsiniz.


Yapmaya çalıştığım şey bu, ancak düşündüğüm kadar kolay değil: stackoverflow.com/questions/3897818/…
Luca

Bu çok eski bir gönderi ancak bizim yaptığımız gibi bunu uygulamak isteyenler için, lütfen XmlSerializer'ın extraTypes parametresine sahip yapıcısının anında oluşturduğu derlemeleri önbelleğe almadığını unutmayın . Bu, bellek sızıntısı için haftalarca hata ayıklamaya mal oluyor. Bu nedenle, kabul edilen yanıtlayıcının koduyla ekstra türleri kullanacaksanız , serileştiriciyi önbelleğe alın . Bu davranış burada belgelenmiştir: support.microsoft.com/en-us/kb/886385
Julien Lebot

3

Cidden, genişletilebilir bir POCO çerçevesi asla güvenilir bir şekilde XML'e serileştirmez. Bunu söylüyorum çünkü birinin geleceğini, sınıfınızı genişleteceğini ve onu mahvedeceğini garanti edebilirim.

Nesne grafiklerinizi serileştirmek için XAML kullanmaya bakmalısınız. Bunu yapmak için tasarlanmıştır, ancak XML serileştirme değildir.

Xaml serileştirici ve seriyi kaldırıcı, jenerikleri sorunsuz bir şekilde, temel sınıfların koleksiyonlarını ve arabirimlerini (koleksiyonların kendileri uyguladığı IListveya uyguladığı sürece) işler IDictionary. Yalnızca okunabilir koleksiyon özelliklerinizi ile işaretlemek gibi bazı uyarılar vardır DesignerSerializationAttribute, ancak bu köşe durumlarını ele almak için kodunuzu yeniden çalışmak o kadar da zor değildir.


Bağlantı
kesik

Oh iyi. Biraz bombalayacağım. Konuyla ilgili pek çok başka kaynak var.

2

Sadece bununla ilgili hızlı bir güncelleme, unutmadım!

Sadece biraz daha araştırma yapıyorum, galip geliyorum, sadece kodu sıralamam gerekiyor.

Şimdiye kadar aşağıdakilere sahibim:

  • XmlSeralizer temelde seri hale olduğu sınıflar bazı kendine has yansıma yapan bir sınıftır. Türe göre serileştirilen özellikleri belirler .
  • Sorunun oluşmasının nedeni, bir tür uyuşmazlığının meydana gelmesidir , BaseType'ı beklemekte ancak gerçekte DerivedType'ı almaktadır. Bunu polimorfik olarak ele alacağını düşünseniz de, tam bir ekstra yük içereceği için değil bunu yapmak için tasarlanmayan yansıtma ve yazım denetimi.

Bu davranış, serileştirici için arabulucu görevi görecek bir proxy sınıfı oluşturularak geçersiz kılınabilir (kod beklemede) gibi görünüyor. Bu temelde türetilmiş sınıfın türünü belirleyecek ve ardından bunu normal olarak serileştirecektir. Bu vekil sınıf, daha sonra bu XML'i ana serileştiriciye yedekleyecek.

Bu alanı izle! ^ _ ^


2

Kesinlikle sorununuza bir çözüm, ancak "taşınabilir" XML biçimini kullanma niyetinizi biraz zayıflatan başka bir sorun var. Programınızın bir sonraki sürümünde sınıfları değiştirmeye karar verdiğinizde kötü bir şey olur ve her iki serileştirme biçimini de desteklemeniz gerekir - yenisi ve eski biçim (çünkü müşterileriniz hala eski dosyalarını / veritabanlarını kullanıyor veya sunucunuz ürününüzün eski sürümünü kullanıyor). Ancak bu serileştiriciyi artık kullanamazsınız çünkü

type.AssemblyQualifiedName

hangisine benziyor

TopNamespace.SubNameSpace.ContainingClass+NestedClass, MyAssembly, Version=1.3.0.0, Culture=neutral, PublicKeyToken=b17a5c561934e089

bu, montaj özelliklerinizi ve sürümünüzü içerir ...

Şimdi, montaj versiyonunuzu değiştirmeye çalışırsanız veya imzalamaya karar verirseniz, bu seriden çıkarma işe yaramayacak ...


1

Buna benzer şeyler yaptım. Normalde yaptığım şey, tüm XML serileştirme özniteliklerinin somut sınıfta olduğundan emin olmak ve sadece bu sınıftaki özelliklerin, serileştirici çağırdığında / serileştirilecek bilgileri almak için temel sınıflara (gerektiğinde) çağrı yapmasını sağlamaktır. bu özellikler. Bu biraz daha fazla kodlama işi, ancak serileştiriciyi sadece doğru şeyi yapmaya zorlamaktan çok daha iyi çalışıyor.


1

Daha da iyisi, notasyonu kullanarak:

[XmlRoot]
public class MyClass {
    public abstract class MyAbstract {} 
    public class MyInherited : MyAbstract {} 
    [XmlArray(), XmlArrayItem(typeof(MyInherited))] 
    public MyAbstract[] Items {get; set; } 
}

2
Sınıflarınızı biliyorsanız bu harika, en zarif çözüm bu. Harici bir kaynaktan yeni miras alınan sınıflar yüklerseniz, maalesef onu kullanamazsınız.
Vladimir
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.