XML 직렬화 및 상속 된 유형


84

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

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

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

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

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

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


1
직렬화하려는 클래스에서 추출 된 관련 코드 스 니펫을 보는 것이 도움이 될 것입니다.
Rex M

나는 다른 사람이 유용하게 찾을 수 느끼기 때문에 나는 재개,하지만 당신이 동의하지 않는 경우 닫 주시기 바랍니다 : 메이트
JamesSugrue

이 스레드에 너무 오랫동안 아무것도 없었기 때문에 약간 혼란 스럽습니까?
Rob Cooper

답변:


54

문제 해결됨!

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

요약하자면 :

목표 :

  • 유지 관리 문제로 인해 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하고 (그렇지 않은 사람들에게는 공격하지 않음) 담당자가 있으면 이것을 닫습니다 :)

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


나는 얼마 전에이 문제에 직면했습니다. 개인적으로 저는 XmlSerializer를 버리고 IXmlSerializable 인터페이스를 직접 사용했습니다. 어쨌든 모든 클래스가이를 구현해야했기 때문입니다. 그렇지 않으면 솔루션이 매우 유사합니다. 그래도 좋은 글 쓰기 :)
Thorarin 2009-06-12

목록을 배열로 변환하는 XML_ 속성을 사용합니다. :)
Arcturus

2
클래스를 동적으로 인스턴스화하려면 매개 변수없는 생성자가 필요하기 때문입니다.
Silas Hansen

1
여보세요! 나는 지금 꽤 오랫동안 이와 같은 해결책을 찾고 있었다. 나는 그것이 훌륭하다고 생각한다! 나는 그것을 사용하는 방법을 알아낼 수 없지만, 예를 들어 주실 수 있습니까? 객체를 포함하는 클래스 또는 목록을 직렬화하고 있습니까?
Daniel

1
좋은 코드. 매개 변수없는 생성자는 선언 private하거나 protected다른 클래스에서 사용할 수 없도록 강제 할 수 있습니다.
tcovo 2011 년

9

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

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

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


이것이 제가하려는 일이지만 생각했던 것처럼 쉽지 않습니다 : stackoverflow.com/questions/3897818/…
Luca

이것은 매우 오래된 게시물이지만 우리처럼 이것을 구현하려는 모든 사람들을 위해 extraTypes 매개 변수를 사용하는 XmlSerializer의 생성자는 생성되는 어셈블리를 즉시 캐시하지 않습니다 . 이로 인해 메모리 누수를 디버깅하는 데 몇 주가 걸립니다. 따라서 허용 된 응답 코드와 함께 추가 유형을 사용 하려면 serializer를 캐시하십시오 . 이 동작은 여기에 설명되어 있습니다 : support.microsoft.com/en-us/kb/886385
줄리앙 Lebot을

3

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

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

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


링크는 죽은 것 같다
bkribbs

아, 그래. 그 비트를 핵폭탄 할게요. 주제에 대한 많은 다른 리소스가 있습니다.

2

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

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

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

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

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

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


2

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

type.AssemblyQualifiedName

이것은

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

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

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


1

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


1

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

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

2
이것은 당신이 당신의 수업을 알고 있다면 가장 훌륭한 솔루션입니다. 외부 소스에서 상속 된 새 클래스를로드하면 안타깝게도 사용할 수 없습니다.
Vladimir
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.