문제

내 말에 이어 이전 질문 저는 개체 모델을 XML로 직렬화하는 작업을 해왔습니다.하지만 이제 문제에 봉착했습니다(놀랍습니다!).

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

나는 관련된 모든 클래스에 XML 속성을 추가하기만 하면 모든 것이 완벽해질 것이라고 생각했습니다.안타깝게도 그렇지 않습니다!

그래서 구글에서 좀 찾아봤는데 이제 이해가 되네요 작동하지 않습니다.그 안에 그만큼 XmlSerializer 실제로 객체를 XML로/에서 직렬화하기 위해 몇 가지 영리한 반사를 수행하고 있으며 추상 유형을 기반으로 하기 때문에 도대체 무엇을 말하는지 알 수 없습니다..괜찮은.

나는 우연히 만났다 이 페이지 CodeProject에서는 많은 도움이 될 것 같지만(아직 완전히 읽거나 사용하지 않음) 이 문제를 StackOverflow 테이블에도 적용하여 깔끔한 해킹/트릭이 있는지 확인하고 싶다고 생각했습니다. 가능한 가장 빠르고 가벼운 방법으로 이를 시작하고 실행하십시오.

한 가지 추가해야 할 점은 내가 하지 마라 아래로 내려가고 싶어 XmlInclude 노선.단순히 결합이 너무 많고 시스템의 이 영역이 집중적으로 개발 중이므로 유지 관리가 정말 골치 아픈 일이 될 것입니다!

도움이 되었습니까?

해결책

문제 해결됨!

좋아, 드디어 거기에 도착했어(물론 많은 도움의 여기!).

요약하자면:

목표:

  • 아래로 내려가고 싶지 않았어 XmlInclude 유지 관리 문제로 인한 경로.
  • 솔루션을 찾은 후에는 이를 다른 애플리케이션에 신속하게 구현하고 싶었습니다.
  • 추상 유형의 컬렉션은 물론 개별 추상 속성도 사용할 수 있습니다.
  • 나는 구체적인 클래스에서 "특별한" 일을 해야하는 것에 대해 별로 신경쓰고 싶지 않았습니다.

확인된 문제/참고 사항:

  • XmlSerializer 꽤 멋진 반성을 하긴 하지만, 그건 매우 추상 유형의 경우 제한적입니다(예:하위 클래스가 아닌 추상 유형 자체의 인스턴스에서만 작동합니다.
  • Xml 특성 데코레이터는 XmlSerializer가 찾은 속성을 처리하는 방법을 정의합니다.물리적 유형도 지정할 수 있지만 이렇게 하면 긴밀한 결합 클래스와 시리얼라이저 사이(좋지 않음)
  • 구현하는 클래스를 생성하여 자체 XmlSerializer를 구현할 수 있습니다. IXml직렬화 가능 .

해결책

나는 작업할 추상 유형으로 제네릭 유형을 지정하는 제네릭 클래스를 만들었습니다.이는 캐스팅을 하드 코딩할 수 있기 때문에 클래스에 추상 유형과 구체적인 유형 사이를 "변환"할 수 있는 기능을 제공합니다.XmlSerializer가 얻을 수 있는 것보다 더 많은 정보를 얻을 수 있습니다.)

그런 다음 IXml직렬화 가능 인터페이스의 경우 이는 매우 간단하지만 직렬화할 때 구체적인 클래스의 유형을 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 선언에 추가하는 것은 쉽습니다!:디

메모:이 코드를 사용하신다면 알려주시면 정말 감사하겠습니다.이는 또한 더 많은 사람들을 지역사회로 유도하는 데 도움이 될 것입니다 :)

지금은 모두 장점과 단점이 있기 때문에 여기서 답변을 어떻게 해야 할지 잘 모르겠습니다.유용하다고 생각되는 항목을 수정하고(그렇지 않은 항목에 대해 불쾌감을 주지 않음) 담당자가 생기면 이 항목을 닫겠습니다. :)

흥미로운 문제와 해결하는 재미가 쏠쏠합니다!:)

다른 팁

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

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

편집하다:이 접근 방식은 XmlInclude 특성 등에 비해 런타임에 가능한 구체적인 유형 목록을 검색하고 컴파일하여 넣을 수 있는 이점이 있다고 덧붙이고 싶습니다.

심각하게도 POCO의 확장 가능한 프레임워크는 결코 XML로 안정적으로 직렬화되지 않습니다.누군가가 와서 수업을 연장하고 망칠 것이라고 장담할 수 있기 때문에 이렇게 말하는 것입니다.

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

Xaml 직렬 변환기 및 역직렬 변환기는 문제 없이 제네릭, 기본 클래스 컬렉션 및 인터페이스 컬렉션도 처리합니다(컬렉션 자체가 구현하는 한). IList 또는 IDictionary).읽기 전용 컬렉션 속성을 DesignerSerializationAttribute, 그러나 이러한 특수한 경우를 처리하기 위해 코드를 재작업하는 것은 그리 어렵지 않습니다.

이에 대한 간단한 업데이트를 잊지 않았습니다!

좀 더 조사해 보면 승자가 될 것 같습니다. 코드를 정렬하면 됩니다.

지금까지 나는 다음을 가지고 있습니다 :

  • 그만큼 XmlSeralizer 기본적으로 직렬화하는 클래스에 대해 멋진 반영을 수행하는 클래스입니다.이를 기반으로 직렬화되는 속성을 결정합니다. 유형.
  • 문제가 발생하는 이유는 유형 불일치가 발생하고 있기 때문입니다. 기본 유형 하지만 실제로는 파생 유형 ..다형성으로 처리할 것이라고 생각할 수도 있지만, 그렇게 하도록 설계되지 않은 전체 추가 반사 및 유형 검사 부하가 포함되기 때문에 그렇지 않습니다.

이 동작은 직렬 변환기의 중개자 역할을 하는 프록시 클래스를 생성하여 재정의(코드 보류)할 수 있는 것으로 보입니다.이는 기본적으로 파생 클래스의 유형을 결정한 다음 이를 정상적으로 직렬화합니다.그런 다음 이 프록시 클래스는 해당 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; } 
}
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top