[c#] XML 직렬화 및 상속 된 유형

이전 질문 에 이어 객체 모델을 XML로 직렬화하는 작업을 해왔습니다. 그러나 나는 이제 문제에 봉착했습니다 (놀람을 깜짝 놀라게합니다!).

내가 가진 문제는 구체적으로 파생 된 유형으로 채워진 추상 기본 클래스 유형의 컬렉션이 있다는 것입니다.

관련된 모든 클래스에 XML 속성을 추가하는 것이 좋을 것이라고 생각했습니다. 슬프게도 그렇지 않습니다!

그래서 저는 Google에서 몇 가지 파고 들었고 이제 작동하지 않는지 이해 합니다. 점에서 (가) XmlSerializer사실에 직렬화하기 위해 일부 영리한 반사를하고 추상적 유형에 따라, 그것이 말하는 무슨 지옥을 알아낼 수 없습니다에 / XML에서, 그 이후 객체 . 좋아.

나는 CodeProject 에서이 페이지 를 보았습니다. 이 페이지 는 많은 도움이 될 것 같습니다 (아직 완전히 읽고 소비하는 것은 아닙니다).하지만이 문제를 StackOverflow 테이블에도 가져 와서 깔끔한 것이 있는지 확인하고 싶습니다. 가능한 한 가장 빠르고 가벼운 방법으로 이것을 시작하고 실행하기 위해 해킹 / 트릭.

나는 또한 추가해야 한가지는 내가 있다는 것입니다 하지 마십시오 아래로 가고 싶은 XmlInclude길을. 너무 많은 커플 링이 있고 시스템의이 영역은 과도하게 개발 중이므로 유지 관리가 정말 골치가 아플 것입니다!



답변

문제 해결됨!

좋아, 그래서 마침내 거기에 도착했습니다 (분명히 여기 에서 많은 도움을 받았습니다 !).

요약하자면 :

목표 :

  • 유지 관리 문제로 인해 XmlInclude 경로를 따르고 싶지 않았습니다 .
  • 솔루션을 찾으면 다른 응용 프로그램에서 빠르게 구현할 수 있기를 원했습니다.
  • 개별 추상 속성뿐만 아니라 추상 유형의 컬렉션을 사용할 수 있습니다.
  • 나는 구체적인 수업에서 “특별한”일을해야하는 것을 정말로 귀찮게하고 싶지 않았습니다.

확인 된 문제 / 참고 사항 :

  • XmlSerializer 는 꽤 멋진 리플렉션을 수행하지만 추상 유형에 관해서 는 매우 제한적입니다 (즉, 하위 클래스가 아닌 추상 유형 자체의 인스턴스에서만 작동합니다).
  • Xml 속성 데코레이터는 XmlSerializer가 찾은 속성을 처리하는 방법을 정의합니다. 물리적 유형도 지정할 수 있지만 이로 인해 클래스와 직렬 변환기간에 긴밀한 결합 이 생성됩니다 (좋지 않음).
  • IXmlSerializable 을 구현하는 클래스를 만들어 자체 XmlSerializer를 구현할 있습니다.

해결책

작업 할 추상 유형으로 제네릭 유형을 지정하는 제네릭 클래스를 만들었습니다. 이렇게하면 캐스팅을 하드 코딩 할 수 있으므로 (즉, XmlSerializer가 할 수있는 것보다 더 많은 정보를 얻을 수 있기 때문에) 추상 유형과 구체적인 유형간에 “변환”할 수있는 기능이 클래스에 제공됩니다.

그런 다음 IXmlSerializable 인터페이스 를 구현했습니다 . 이것은 매우 간단하지만 직렬화 할 때 구체적인 클래스 유형을 XML에 기록해야합니다. 그래야 직렬화 해제 할 때 다시 캐스팅 할 수 있습니다. 또한 두 클래스가있는 어셈블리가 다를 수 있으므로 정규화 되어야합니다 . 물론 여기에서 발생해야 할 약간의 유형 검사 및 작업이 있습니다.

XmlSerializer는 캐스트 할 수 없으므로이를 수행하는 코드를 제공해야하므로 암시 적 연산자가 오버로드됩니다 (이 작업을 수행 할 수 있다는 사실조차 몰랐습니다!).

AbstractXmlSerializer의 코드는 다음과 같습니다.

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

그렇다면 거기에서 XmlSerializer가 기본값이 아닌 직렬 변환기와 함께 작동하도록 어떻게 지시합니까? Xml 속성 유형 속성 내에서 유형을 전달해야합니다. 예를 들면 다음과 같습니다.

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

여기에서 컬렉션과 단일 속성이 노출되는 것을 볼 수 있으며, 우리가해야 할 일은 매개 변수라는 형식 을 Xml 선언에 쉽게 추가하는 것뿐입니다! :디

참고 :이 코드를 사용하는 경우 정말 감사합니다. 또한 더 많은 사람들을 커뮤니티로 유도하는 데 도움이 될 것입니다. 🙂

이제는 모두 장단점을 가지고 있기 때문에 여기에서 답변으로 무엇을 해야할지 확신 할 수 없습니다. 나는 내가 유용하다고 생각하는 것을 upmod하고 (그렇지 않은 사람들에게는 공격하지 않음) 담당자가 있으면 이것을 닫습니다 🙂

재미있는 문제와 해결하기 좋은 재미! 🙂


답변

살펴 봐야 할 한 가지는 XmlSerialiser 생성자에서 serialiser가 해결하기 어려울 수있는 형식 배열을 전달할 수 있다는 사실입니다. 컬렉션 또는 복잡한 데이터 구조 집합을 직렬화해야하고 이러한 유형이 다른 어셈블리 등에있는 경우이를 여러 번 사용해야했습니다.

extraTypes 매개 변수가있는 XmlSerialiser 생성자

편집 :이 접근 방식은 XmlInclude 속성 등에 비해 이점이 있으므로 런타임에 가능한 구체적인 유형 목록을 발견하고 컴파일하고 채워 넣을 수 있습니다.


답변

진지하게, 확장 가능한 POCO 프레임 워크는 XML로 안정적으로 직렬화되지 않습니다. 나는 누군가가 와서 당신의 수업을 연장하고 엉망으로 만들 것이라고 보장 할 수 있기 때문에 이것을 말합니다.

개체 그래프를 직렬화하는 데 XAML 사용을 고려해야합니다. 이 작업을 수행하도록 설계되었지만 XML 직렬화는 그렇지 않습니다.

Xaml serializer 및 deserializer는 문제없이 제네릭, 기본 클래스 및 인터페이스 컬렉션도 처리합니다 (컬렉션 자체가 IList또는 구현하는 한 IDictionary). 읽기 전용 컬렉션 속성을으로 표시하는 것과 같은 몇 가지주의 사항이 DesignerSerializationAttribute있지만 이러한 코너 케이스를 처리하기 위해 코드를 재 작업하는 것은 그리 어렵지 않습니다.


답변

이것에 대한 빠른 업데이트, 나는 잊지 않았습니다!

좀 더 조사를하고, 내가 승자 인 것처럼 보이며, 코드를 정렬하면됩니다.

지금까지 다음이 있습니다.

  • XmlSeralizer는 기본적으로는 직렬화 된 클래스에 대한 몇 가지 멋진 반영을하는 클래스입니다. Type을 기반으로 직렬화되는 속성을 결정합니다 .
  • 문제가 발생하는 이유는 유형 불일치가 발생했기 때문에 BaseType을 예상 하지만 실제로 DerivedType을 수신합니다 . 리플렉션 및 유형 검사를 수행하도록 설계되지 않았습니다.

이 동작은 serializer의 중개자 역할을하는 프록시 클래스를 만들어 재정의 (코드 보류) 할 수있는 것으로 보입니다. 이것은 기본적으로 파생 클래스의 유형을 결정한 다음 정상적으로 직렬화합니다. 그러면이 프록시 클래스는 해당 XML 백업 라인을 주 직렬 변환기에 공급합니다.

이 공간을보십시오! ^ _ ^


답변

확실히 문제에 대한 해결책이지만 “이동 가능한”XML 형식을 사용하려는 의도를 다소 약화시키는 또 다른 문제가 있습니다. 다음 버전의 프로그램에서 클래스를 변경하기로 결정하고 새 형식과 이전 형식의 직렬화를 모두 지원해야 할 때 나쁜 일이 발생합니다 (클라이언트가 여전히 이전 파일 / 데이터베이스를 사용하거나 제품의 이전 버전을 사용하는 서버). 하지만이 직렬화기를 더 이상 사용할 수 없습니다.

type.AssemblyQualifiedName

이것은

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

여기에는 어셈블리 속성과 버전이 포함됩니다.

이제 어셈블리 버전을 변경하려고하거나 서명하기로 결정하면이 역 직렬화가 작동하지 않습니다.


답변

나는 이와 비슷한 일을했습니다. 내가 일반적으로하는 일은 모든 XML 직렬화 특성이 구체적인 클래스에 있는지 확인하고 해당 클래스의 속성을 기본 클래스 (필요한 경우)를 통해 호출하여 직렬화가 호출 할 때 역 직렬화 될 정보를 검색하는 것입니다. 그 속성. 약간 더 많은 코딩 작업이지만 직렬 변환기가 올바른 작업을 수행하도록 강제하는 것보다 훨씬 더 잘 작동합니다.


답변

표기법을 사용하면 더 좋습니다.

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