XML 직렬화 및 상속된 유형
이전 질문에 이어서 객체 모델을 XML로 직렬화하는 작업을 하고 있습니다. 하지만 지금 문제(quelle surprise!)에 부딪혔습니다.
문제는 추상적인 기본 클래스 유형인 컬렉션이 있다는 것인데, 이 컬렉션은 구체적인 파생 유형으로 채워집니다.
저는 모든 클래스에 XML 속성을 추가하면 모든 것이 잘 될 것이라고 생각했습니다.안타깝게도, 그렇지 않습니다!
그래서 구글을 좀 조사해봤는데 왜 안되는지 이제 이해가 갑니다.는 사실 XML에 객체를 직렬화하기 위해 약간의 영리한 반사를 하고 있고 추상적 유형을 기반으로 하기 때문에 도대체 무엇과 대화를 나누고 있는지 알 수 없습니다.좋아.
CodeProject에서 이 페이지를 보게 되었는데, 이 페이지는 많은 도움이 될 것으로 보입니다. (아직 완전히 읽기/소모는 것은 아니지만) StackOverflow 테이블에도 이 문제를 도입하여 가능한 한 가장 빠르고 가벼운 방법으로 실행할 수 있는 깔끔한 해킹/트릭이 있는지 확인하고자 합니다.
한 가지 더 덧붙여야 할 것은, 저는 그 아래로 내려가고 싶지 않다는 것입니다.XmlInclude
경로. 그것과 너무 많은 결합이 있고, 시스템의 이 부분은 심한 개발 중에 있어서, 그것은 정말로 유지보수의 골칫거리가 될 것입니다!
문제 해결!
좋아요, 드디어 도착했어요 (인정하건대 여기서 많은 도움을 받았어요!).
요약하면 다음과 같습니다.
목표:
- 유지보수 두통 때문에 XmlInclude 노선을 가기 싫었습니다.
- 해결책이 발견되면 다른 애플리케이션에서 신속하게 구현할 수 있기를 원했습니다.
- 개별 추상 속성뿐만 아니라 추상 유형의 컬렉션도 사용할 수 있습니다.
- 저는 구체적인 수업에서 "특별한" 일들을 해야 하는 것에 대해 별로 신경 쓰고 싶지 않았습니다.
식별된 문제/주의 사항:
- XmlSerializer는 꽤 멋진 반영을 수행하지만 추상 유형에 관해서는 매우 제한적입니다(즉, 서브클래스가 아닌 추상 유형 자체의 인스턴스에서만 작동함).
- Xml 특성 데코레이터는 XmlSerializer가 찾은 속성을 처리하는 방법을 정의합니다.물리적 유형도 지정할 수 있지만 클래스와 직렬화기 사이에 긴밀한 연결이 발생합니다(좋지 않음).
- IXmlSerializable을 구현하는 클래스를 만들어 자체 XmlSerializer를 구현할 수 있습니다.
더 솔루션
제네릭 클래스를 만들었습니다. 여기서 제네릭 유형을 작업할 추상 유형으로 지정합니다.이를 통해 클래스는 주조물을 하드 코딩할 수 있으므로 추상 유형과 구체 유형 사이에서 "번역"할 수 있습니다(즉, XmlSerializer가 얻을 수 있는 것보다 더 많은 정보를 얻을 수 있습니다).
그런 다음 IXml Serializable 인터페이스를 구현했는데, 이는 매우 간단하지만 직렬화할 때는 구체적인 클래스의 유형을 XML에 기록해야 하므로 디시리얼화할 때 다시 캐스트할 수 있습니다.또한 두 클래스가 있는 어셈블리가 다를 가능성이 있으므로 완전히 적합해야 합니다.물론 여기에 약간의 유형 점검과 발생해야 하는 것들이 있습니다.
XmlSerializer는 캐스트할 수 없으므로 코드를 제공해야 하므로 암시적 연산자가 오버로드됩니다(당신이 이렇게 할 수 있는지도 몰랐어요!).
AbstractXml Serializer의 코드는 다음과 같습니다.
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 Attributes type 속성 내에 유형을 전달해야 합니다.
[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 선언에 type named parameter만 추가하면 됩니다. easy! :D
참고: 이 코드를 사용해 주신다면 큰 소리로 외쳐주시면 감사하겠습니다.또한 더 많은 사람들을 지역사회로 이끄는 데 도움이 될 것입니다 :)
자, 하지만 그들은 모두 장단점이 있기 때문에 여기서 답변을 어떻게 해야 할지 잘 모르겠습니다.유용하다고 생각되는 것들을 수정하고(그렇지 않은 것들에 대해서는 불쾌감을 주지 않음) 담당자가 있으면 이 작업을 종료하겠습니다. :)
재미있는 문제와 해결하기 좋은 재미! :)
한 가지 주의해야 할 점은 XmlSerialiser Constructor에서 직렬화기가 해결하기 어려울 수 있는 유형의 배열을 전달할 수 있다는 것입니다.여러 번 데이터 구조의 수집 또는 복잡한 집합을 직렬화해야 하고 이러한 유형이 서로 다른 어셈블리 등에서 사용해야 했습니다.
추가 기능이 있는 Xml Serialiser Constructor유형 매개변수
편집: 이 접근 방식은 실행 시 가능한 구체적인 유형의 목록을 발견하고 컴파일하여 입력하는 방법을 고안할 수 있는 XmlInclude 속성 등에 비해 이점이 있다고 덧붙이고 싶습니다.
진지하게, 확장 가능한 POC의 프레임워크는 XML로 안정적으로 직렬화되지 않습니다.누군가가 와서 수업을 연장하고, 수업을 방해할 거라고 장담할 수 있기 때문에 하는 말입니다.
객체 그래프를 직렬화할 때 XAML을 사용하는 방법을 고려해야 합니다.이를 위해 설계된 반면 XML 직렬화는 그렇지 않습니다.
및 및 가 Xaml serializer deserializer 을 합니다 되는 가 는 을 합니다 되는 가 IList
아니면IDictionary
에 를 하는 과 이 에 이 에 DesignerSerializationAttribute
이러한 코너 를 다시 것은 어렵지 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
이것에 대한 간단한 업데이트, 나는 잊지 않았습니다!
조사를 좀 더 해보고 있어요, 제가 우승자가 될 것 같아요, 코드를 분류해야 해요.
지금까지 다음과 같은 것들이 있습니다.
- XmlSeralizer는 기본적으로 직렬화 중인 클래스를 잘 반영하는 클래스입니다.유형을 기준으로 직렬화되는 속성을 결정합니다.
- 문제가 발생하는 이유는 유형 불일치가 발생하고 있기 때문입니다. BaseType을 예상하고 있지만 실제로 DerivatedType을 수신하기 때문입니다.다형적으로 처리할 것이라고 생각할 수도 있지만, 그렇지 않은 이유는 그것이 의도된 것이 아닌, 전체적인 추가적인 성찰과 유형 확인을 수반하기 때문입니다.
직렬화기의 중간 역할을 하도록 프록시 클래스를 만들어 이 동작을 재정의(코드 보류)할 수 있습니다.이렇게 하면 기본적으로 파생된 클래스의 유형을 결정한 다음 해당 클래스를 정상으로 직렬화합니다.그러면 이 프록시 클래스는 해당 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; }
}
언급URL : https://stackoverflow.com/questions/20084/xml-serialization-and-inherited-types
'programing' 카테고리의 다른 글
jquery로 클릭 이벤트를 변경하는 방법? (0) | 2023.09.16 |
---|---|
Oracle DECODE의 표준 SQL 대안 (0) | 2023.09.16 |
Powershell을 사용하여 WMI 또는 원격 없이 원격으로 서비스 중지 (0) | 2023.09.11 |
WebSphere to Oracle - 올바른 암호를 사용할 수 없습니다. (0) | 2023.09.11 |
SEC7118: XMLHttpRequest CORS - IE 콘솔 메시지 (0) | 2023.09.11 |