问题描述
编辑:问题的根本原因
我正在开发一个应用程序,它通过 System.Messaging
使用 XmlMessageFormatter
和 XML 序列化。
public class Class1{
public long Id1;
}
我还想发送另一个对象,比如 Class16
,有另一个 ID 字段:
public class Class16{
public long Id16;
}
在 XML 中,两者都需要如下所示:
<HM>Human_Message
<ID>Own_Identifier</ID>
</HM>
为了实现这一点,我正在使用以下类似 [Xml]
的配置:
Class1:
[XmlRoot(ElementName = "HM")]
public class Class1{
[XmlElement(ElementName = "ID")]
public long Id1;
}
Class16:
[XmlRoot(ElementName = "HM")]
public class Class16{
[XmlElement(ElementName = "ID")]
public long Id16;
}
如您所见,两个类的 XML 正文确实相同。
这甚至可能吗?
编辑:原始问题
我有一个基本类(简单类),从中继承了几个子类(大约 27 个)。
我使用标准 C# System.Messaging
系统来回发送对象。
非常简化:
发送方:
subClass1 Obj1 = subClass1(...);
...
Basic_Class Obj_To_Be_Sent = Basic_Class(Obj1);
System.Messaging.Message message = new System.Messaging.Message(Obj_To_Be_Sent);
obj_MessageQueue.Send(message,...);
检查 Obj_To_Be_Sent
时,类型正确。
发送后,当我查看计算机管理、服务和应用程序、消息队列、...、属性,我看到了消息,但我无法验证类型是否仍然正确。
接收方:
我有一个 _xmlmessageformatter
,其中包含(其中包括):
System.Type[] messageTypes = new System.Type[27];
messageTypes[0] = typeof(SubClass1);
...
messageTypes[15] = typeof(SubClass16);
...
message = this._receiveQueue.Receive();
Basic_Class general = (Basic_Class)this._xmlmessageformatter.Read(message);
Type objectType= general.GetType();
令我惊讶的是,objectType
是错误的(据信是 SubClass16
)。
此应用程序以前运行良好,但现在似乎出现故障。我遇到的最大问题是我不知道如何检查发送消息和获取接收消息类型之间的步骤。
是否有人了解计算机管理、服务和应用程序、消息队列、...,我如何检查对象是否发送端打字可以吗?
有人了解_xmlFormatter.Read()
和GetType()
吗? (已经在 Read()
之后,观察窗口提到 general
的类型是错误的)
提前致谢
更多调查后编辑
与此同时,我发现 [XmlRoot]
条目导致了上述问题:我一直在为不同的类使用相同的 [XmlRoot]
条目。
有办法区分吗?
为了您的信息,我已经尝试了以下但没有奏效:
Class1:
[XmlRoot(ElementName = "HM",DataType = "subClass1",Namespace="Namespace")]
public class subClass1 : Basic_Class
Class2:
[XmlRoot(ElementName = "HM",DataType = "subClass16",Namespace="Namespace")]
public class subClass16 : Basic_Class
而 _xmlFormatter.targettypes
包含以下条目:
Name = "subClass1" FullName="Namespace.Class1"
Name = "subClass16" FullName="Namespace.Class16"
有人有什么想法吗?
解决方法
TL/DR
XmlMessageFormatter
通过检查 XML root element 名称和命名空间 URI 并在提供的 TargetTypes
中找到兼容类型来识别接收到的对象类型:
指定格式化程序将从提供的消息中反序列化的可能类型集。
因此,您需要为每个派生类型指定不同的 XmlRootAttribute.ElementName
根元素名称,而不是为所有这些类型使用相同的名称 "HM"
。这样做后,您应该能够使用 XmlMessageFormatter
发送和接收它们而不会丢失类型信息。
如果由于某种原因无法完成并且您需要在所有类上使用相同的根名称“HM”,您将需要实现自己的自定义IMessageFormatter
基于 XmlMessageFormatter
,它通过某种其他机制(例如 xsi:type
属性)对类型进行编码。
说明
您正在使用 XmlMessageFormatter
发送和接收格式为 XML 的多态类型层次结构的实例。这个序列化器在内部使用 XmlSerializer
来序列化到 XML 和从 XML 反序列化。这个序列化器支持两种不同的机制来交换多态类型:
-
可以通过为每种类型使用不同的(根)元素名称来指定类型信息。
由于根元素名称默认由类名给出,您可能不需要通过元数据指示不同的根元素名称,但如果这样做,请使用
XmlRootAttribute.ElementName
:在 XML 文档实例中生成和识别的 XML 根元素的名称。默认为序列化类的名称。
如果您选择这种机制,您的类将如下所示:
[XmlRoot(ElementName = "Basic_Class",Namespace="Namespace")] public class Basic_Class { } [XmlRoot(ElementName = "Class1",Namespace="Namespace")] public class Class1 : Basic_Class { } [XmlRoot(ElementName = "Class16",Namespace="Namespace")] public class Class16 : Basic_Class { }
使用此机制时,为要序列化的具体类型构造一个
XmlSerializer
。 -
如果为所有子类型发出一个公共根元素,则可以通过
xsi:type
属性指定类型信息。此属性是
{http://www.w3.org/2001/XMLSchema-instance}type
的缩写,是一个 w3c standard attribute,它允许元素显式断言其类型,例如当它是预期元素类型的多态子类型时。XmlSerializer
supports this attribute 并将使用它来确定要为这种多态类型反序列化的对象的实际类型。如果您选择这种机制,您的类将如下所示:
[XmlRoot(ElementName = "Basic_Class",Namespace="Namespace")] [XmlInclude(typeof(Class1)),XmlInclude(typeof(Class16))] // Include all subtypes here! public class Basic_Class { } [XmlRoot(ElementName = "Class1",Namespace="Namespace")] public class Class16 : Basic_Class { }
使用此机制时,为共享基类型
Basic_Class
而不是具体类型构造序列化器。
但是,在这两种机制中,XmlMessageFormatter
只支持第一种,从 reference source 可以看出。 Write(Message message,object obj)
只是为传入对象的具体类型构造或使用默认序列化程序:
Type serializedType = obj.GetType();
XmlSerializer serializer = null;
if (this.targetSerializerTable.ContainsKey(serializedType))
serializer = (XmlSerializer)this.targetSerializerTable[serializedType];
else
{
serializer = new XmlSerializer(serializedType);
this.targetSerializerTable[serializedType] = serializer;
}
serializer.Serialize(stream,obj);
由于序列化程序是使用 obj.GetType()
构造的,因此根元素名称将是为派生的具体类指定的名称,并且不会包含 xsi:type
信息。
Read(Message message)
方法循环遍历为 TargetTypes
构造的默认序列化器,并使用 XmlSerializer.CanDeserialize(XmlReader)
为其返回 true
的第一个:
foreach (XmlSerializer serializer in targetSerializerTable.Values)
{
if (serializer.CanDeserialize(reader))
return serializer.Deserialize(reader);
}
这个方法反过来简单地检查根元素名称和命名空间是否与要反序列化的类型兼容。这就是为什么您需要为每个具体类型使用不同的根元素名称。
实现您自己的 IMessageFormatter
。
如上所述,XmlMessageFormatter
仅通过不同的根元素名称支持多态性。如果这是不可接受的,您将需要实现您的 IMessageFormatter
,通过某种其他机制(例如上述 xsi:type
属性)对类型进行编码。
例如,以下 IMessageFormatter
支持通过提供的 XmlSerializer
进行序列化和反序列化:
public class OverrideXmlMessageFormatter : IMessageFormatter
{
readonly XmlSerializer serializer;
public OverrideXmlMessageFormatter(XmlSerializer serializer)
{
if (serializer == null)
throw new ArgumentNullException();
this.serializer = serializer;
}
#region IMessageFormatter Members
public bool CanRead(Message message)
{
if (message == null)
throw new ArgumentNullException();
var stream = message.BodyStream;
bool canRead;
try
{
using (var reader = XmlReader.Create(message.BodyStream))
canRead = serializer.CanDeserialize(reader);
}
catch (Exception)
{
canRead = false;
}
message.BodyStream.Position = 0; // reset stream in case CanRead is followed by Deserialize
return canRead;
}
public object Read(Message message)
{
if (message == null)
throw new ArgumentNullException();
using (var reader = XmlReader.Create(message.BodyStream))
return serializer.Deserialize(reader);
}
public void Write(Message message,object obj)
{
if (message == null || obj == null)
throw new ArgumentNullException();
var stream = new MemoryStream();
serializer.Serialize(stream,obj);
message.BodyStream = stream;
message.BodyType = 0;
}
#endregion
#region ICloneable Members
public object Clone()
{
return new OverrideXmlMessageFormatter(serializer);
}
#endregion
}
然后要发送和接收 Class1
和/或 Class16
类型的消息,请定义以下 XmlSerializerCache.MessagingSerializer
:
public static class XmlSerializerCache
{
static XmlSerializer CreateSharedSerializer(Type baseType,Type[] extraTypes,string rootName,string rootNamespace)
{
// baseType could be typeof(object) if there is no common base type.
return new XmlSerializer(baseType,null,extraTypes,new XmlRootAttribute { ElementName = rootName,Namespace = rootNamespace },null);
}
static XmlSerializer serializer = CreateSharedSerializer(
// The base type for the classes you want to send. Could be object if there is no more derived base type.
typeof(object),// Add all the derived types of the classes you want to send
new[] { typeof(Class1),typeof(Class16) },// Your required root element name.
"MH",// Your required root element namespace.
"");
// The serializer must be statically cached to avoid a severe memory leak,see https://stackoverflow.com/questions/23897145/memory-leak-using-streamreader-and-xmlserializer
public static XmlSerializer MessagingSerializer { get { return serializer; } }
}
并设置
IMessageFormatter _xmlMessageFormatter = new OverrideXmlMessageFormatter(XmlSerializerCache.MessagingSerializer);
您需要在发送方和接收方都使用此格式化程序。
发送的 XML 看起来像(对于 Class1
):
<MH xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xsi:type="Class1">
<ID>10101</ID>
</MH>
注意事项
-
XmlRootAttribute.DataType
不用于指定多态类型信息。相反,它可用于指示元素包含某些特定 XML 原始类型的值,例如dateTime
、duration
等:一种 XSD(XML 架构文档)数据类型。
因此,设置此值对您的应用程序没有帮助。
-
一些关于
XmlSerializer
和多态性的相关问题包括: -
使用带有属性覆盖的
XmlSerializer
序列化为 XML 时,您必须静态缓存并重用序列化程序以避免严重的内存泄漏。请参阅 Memory Leak using StreamReader and XmlSerializer 了解原因。 -
如果您确实实现了自己的
IMessageFormatter
,您可以假设使用不同的序列化程序(例如 Json.NET)实现自己的消息传递格式。