.NET 不能反序列化嵌套结构?
.NET can't deserialize nested structs?
我正在 运行 解决让 C#(VS2008、Compact Framework、.NET 是版本 3.5 SP1)成功反序列化嵌套结构的问题。当我 运行ning 在移动设备的模拟器上(我使用的是 "Pocket PC 2003 Second Edition" 模拟器)时,这个问题只出现在 CF 中,完全相同的代码 运行ning 在我的 Windows盒子没有同样的问题
这是我的代码:
public struct Fred
{
public string Name;
}
public struct Middle
{
public Fred[] Freds;
}
public struct Top
{
public Middle Middle;
public Fred[] Freds;
}
public static void Test()
{
Top top = new Top();
top.Middle.Freds = new Fred[2];
top.Middle.Freds[0].Name = "Fred20";
top.Middle.Freds[1].Name = "Fred21";
top.Freds = new Fred[2];
top.Freds[0].Name = "Fred10";
top.Freds[1].Name = "Fred11";
StringBuilder sb = new StringBuilder();
System.Xml.Serialization.XmlSerializer x = new System.Xml.Serialization.XmlSerializer(top.GetType());
using (StringWriter sw = new StringWriter(sb))
{
x.Serialize(sw, top);
}
string xml = sb.ToString();
string[] lines = xml.Split(new char[] { '\r', '\n' });
foreach (string line in lines)
{
Debug.WriteLine(" " + line.Trim());
}
MemoryStream ms = new MemoryStream(System.Text.Encoding.ASCII.GetBytes(xml));
StreamReader sr = new StreamReader(ms);
object o = x.Deserialize(sr);
Debug.WriteLine("Deserialized into " + o);
Top go2 = (Top)o;
if (go2.Freds == null)
Debug.WriteLine(" go2.Freds is null");
else
Debug.WriteLine(" go2.Freds[0].Name is \"" + go2.Freds[0].Name + "\"");
if (go2.Middle.Freds == null)
Debug.WriteLine(" go2.Middle.Freds is null");
else
Debug.WriteLine(" go2.Middle.Freds[0].Name is \"" + go2.Middle.Freds[0].Name + "\"");
}
当我 运行 这个时,它创建的 XML 看起来不错:
<?xml version="1.0" encoding="utf-16"?>
<Top xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Middle>
<Freds>
<Fred>
<Name>Fred20</Name>
</Fred>
<Fred>
<Name>Fred21</Name>
</Fred>
</Freds>
</Middle>
<Freds>
<Fred>
<Name>Fred10</Name>
</Fred>
<Fred>
<Name>Fred11</Name>
</Fred>
</Freds>
</Top>
但是 C# 无法成功反序列化这个 XML - 控制台输出是这样的:
Deserialized into Top
go2.Freds[0].Name is "Fred10"
go2.Middle.Freds is null
xsd有类似问题:
<?xml version="1.0" encoding="utf-8"?>
<xs:schema id="Top" xmlns="" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xs:element name="Top" msdata:IsDataSet="true" msdata:UseCurrentLocale="true">
<xs:complexType>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="Middle">
<xs:complexType>
<xs:sequence>
<xs:element name="Freds" minOccurs="0" maxOccurs="unbounded">
<xs:complexType>
<xs:sequence>
<xs:element name="Fred" minOccurs="0" maxOccurs="unbounded">
<xs:complexType>
<xs:sequence>
<xs:element name="Name" type="xs:string" minOccurs="0" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:choice>
</xs:complexType>
</xs:element>
</xs:schema>
我刚刚遇到了 C# 错误吗?还是我遗漏了一些明显的东西?
注意:使用两次名称不是问题,如果我创建一个名为George 的结构与Fred 相同,并将Middle 的内容更改为public George[] George,问题是好多了。
TLDR(对于撇渣器):这 post 由两部分组成。
第 1 部分:Protobuf 快速介绍。这里用到了属性。
第 2 部分:问题的实际答案:在不修改继承库的情况下配置序列化
好的,我试试看。
问题似乎是您使用的是 Compact Framework,它不具有与完整 .NET Framework 相同的 serialization/deserialization 功能。所以我们需要一些自定义序列化。
按照 Compact Framework 的理念,我的猜测是您还想要性能良好且占用空间小的东西。所以我选择了 Protobuf for the task (which is also approximately 12 times faster than XmlSerializer)
你可以通过运行这个命令来安装它:
Install-Package protobuf-net
让我们从简单的方法开始 - 通过向模型添加属性。
接下来是没有属性的配置,正如您指出的那样,原始模型不能被修改。t/shouldn。这只是为了说明。
用适当的属性装饰后,您的模型将如下所示:
第 1 部分:配置属性。
我再说一遍,这部分仅用于说明目的 - 请继续阅读 "Configuration without attributes"
[ProtoContract]
public struct Fred
{
[ProtoMember(1)]
public string Name;
}
[ProtoContract]
public struct Middle
{
[ProtoMember(1)]
public Fred[] Freds;
}
[ProtoContract]
public struct Top
{
[ProtoMember(1)]
public Middle Middle;
[ProtoMember(2)]
public Fred[] Freds;
}
这里唯一要注意的是编号成员的用法,称为键。在 JSON 或 XML 序列化的情况下,这与给它们 属性 名称本质上是一样的,除了这是 protobuf 的方法。您只需为同一 class 中的每个成员分配一个唯一的整数值,大多数情况下您就完成了。
为了方便起见,让我们添加一个简单的构建器,我们可以从中实例化一个类似于您示例中的 Top
:
public class TopTestBuilder
{
public Top BuildDefaultTestTop()
{
var top = new Top
{
Middle = new Middle
{
Freds = new[]
{
new Fred {Name = "Fred20"},
new Fred {Name = "Fred21"}
}
},
Freds = new[]
{
new Fred {Name = "Fred10"},
new Fred {Name = "Fred11"}
}
};
return top;
}
}
我们可以像这样序列化它:
Top topIn = new TopTestBuilder().BuildDefaultTestTop();
string serialized;
using (var stream = new MemoryStream())
{
Protobuf.Serializer.Serialize(stream, topIn);
stream.Position = 0;
var reader = new StreamReader(stream);
serialized = reader.ReadToEnd();
}
// Output: "\nDC4\n\b\nACKFred20\n\b\nACKFred21DC2\b\nACKFred10DC2\b\nACKFred11"
并且反序列化它是这样的:
Top topOut;
using (var stream = new MemoryStream())
{
var writer = new StreamWriter(stream);
writer.Write(serialized);
writer.Flush();
stream.Position = 0;
topOut = Protobuf.Serializer.Deserialize<Top>(stream);
}
如您所见,MemoryStreams
有一些管道,但除此之外,其他类型的序列化工作方式看起来应该很熟悉。同样,一切都可以通过配置自定义 TypeModel
来完成,从而使序列化与模型完全解耦。
第 2 部分:没有属性的配置
默认情况下,Protobuf 使用属性定义 TypeModel
,然后将其存储在 ProtoBuf.Meta.RuntimeTypeModel.Default
中。当您直接调用静态 Protobuf.Serializer
时,将使用此 属性。
我们也可以定义我们自己的。花了一些时间(自我注意:RTFM)让它工作,但结果几乎一样简单:
var model = TypeModel.Create();
// The first parameter (maps to ProtoContractAttribute) is the Type to be included.
// The second parameter determines whether to apply default behavior,
// based on the attributes. Since we're not using those, this has no effect.
model.Add(typeof(Fred), false);
model.Add(typeof(Middle), false);
model.Add(typeof(Top), false);
// The newly added MetaTypes can be accessed through their respective Type indices.
// The first parameter is the unique member number, similar to ProtoMemberAttribute.
// The second parameter is the name of the member as it is declared in the class.
// When the member is a list:
// The third parameter is the Type for the items.
// The fourth parameter is the Type for the list itself.
model[typeof(Fred)].Add(1, "Name");
model[typeof(Middle)].Add(1, "Freds", typeof(Fred), typeof(Fred[]));
model[typeof(Top)].Add(1, "Middle");
model[typeof(Top)].Add(2, "Freds", typeof(Fred), typeof(Fred[]));
现在我们所要做的就是为两个函数更改一行代码:
序列化:
Top topIn = new TopTestBuilder().BuildDefaultTestTop();
string serialized;
using (var stream = new MemoryStream())
{
model.Serialize(stream, top);
stream.Position = 0;
var reader = new StreamReader(stream);
serialized = reader.ReadToEnd();
}
反序列化:
Top topOut;
using (var stream = new MemoryStream())
{
var writer = new StreamWriter(stream);
writer.Write(serialized);
writer.Flush();
stream.Position = 0;
topOut = (Top) _model.Deserialize(stream, null, typeof (Top));
}
它的工作原理是一样的。也许添加一个 class 来保持组织 - 给它两个 public 方法 Serialize
和 Deserialize
,以及一个私有方法 BuildTypeModel
(从构造函数调用和存储在序列化程序的字段中?)
您的调用代码最终看起来像这样:
var serializer = new CustomProtoBufSerializer();
var serialized = serializer.Serialize(someClassInput);
SomeClass someClassOutput = serializer.Deserialize(serialized);
有一件事很快就变得清晰了——Protobuf 还没有像大多数 JSON 和 XML 序列化程序那样得到彻底的记录和测试。这一点,连同序列化结果对人类来说是不可读的,在某些情况下可能是一个缺点。除此之外,它看起来速度快、重量轻并且与许多不同的环境兼容。
自动类型解析的缺失让我有点困扰,所以我去寻找并发现了一些看起来很有趣的东西:Protobuf T4 TypeModel Generator。我还没来得及尝试。如果人们有兴趣,我可能会稍后再做,并用更通用的解决方案更新答案。
如果您在使用它时遇到任何问题,请告诉我。
我正在 运行 解决让 C#(VS2008、Compact Framework、.NET 是版本 3.5 SP1)成功反序列化嵌套结构的问题。当我 运行ning 在移动设备的模拟器上(我使用的是 "Pocket PC 2003 Second Edition" 模拟器)时,这个问题只出现在 CF 中,完全相同的代码 运行ning 在我的 Windows盒子没有同样的问题
这是我的代码:
public struct Fred
{
public string Name;
}
public struct Middle
{
public Fred[] Freds;
}
public struct Top
{
public Middle Middle;
public Fred[] Freds;
}
public static void Test()
{
Top top = new Top();
top.Middle.Freds = new Fred[2];
top.Middle.Freds[0].Name = "Fred20";
top.Middle.Freds[1].Name = "Fred21";
top.Freds = new Fred[2];
top.Freds[0].Name = "Fred10";
top.Freds[1].Name = "Fred11";
StringBuilder sb = new StringBuilder();
System.Xml.Serialization.XmlSerializer x = new System.Xml.Serialization.XmlSerializer(top.GetType());
using (StringWriter sw = new StringWriter(sb))
{
x.Serialize(sw, top);
}
string xml = sb.ToString();
string[] lines = xml.Split(new char[] { '\r', '\n' });
foreach (string line in lines)
{
Debug.WriteLine(" " + line.Trim());
}
MemoryStream ms = new MemoryStream(System.Text.Encoding.ASCII.GetBytes(xml));
StreamReader sr = new StreamReader(ms);
object o = x.Deserialize(sr);
Debug.WriteLine("Deserialized into " + o);
Top go2 = (Top)o;
if (go2.Freds == null)
Debug.WriteLine(" go2.Freds is null");
else
Debug.WriteLine(" go2.Freds[0].Name is \"" + go2.Freds[0].Name + "\"");
if (go2.Middle.Freds == null)
Debug.WriteLine(" go2.Middle.Freds is null");
else
Debug.WriteLine(" go2.Middle.Freds[0].Name is \"" + go2.Middle.Freds[0].Name + "\"");
}
当我 运行 这个时,它创建的 XML 看起来不错:
<?xml version="1.0" encoding="utf-16"?>
<Top xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Middle>
<Freds>
<Fred>
<Name>Fred20</Name>
</Fred>
<Fred>
<Name>Fred21</Name>
</Fred>
</Freds>
</Middle>
<Freds>
<Fred>
<Name>Fred10</Name>
</Fred>
<Fred>
<Name>Fred11</Name>
</Fred>
</Freds>
</Top>
但是 C# 无法成功反序列化这个 XML - 控制台输出是这样的:
Deserialized into Top
go2.Freds[0].Name is "Fred10"
go2.Middle.Freds is null
xsd有类似问题:
<?xml version="1.0" encoding="utf-8"?>
<xs:schema id="Top" xmlns="" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xs:element name="Top" msdata:IsDataSet="true" msdata:UseCurrentLocale="true">
<xs:complexType>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="Middle">
<xs:complexType>
<xs:sequence>
<xs:element name="Freds" minOccurs="0" maxOccurs="unbounded">
<xs:complexType>
<xs:sequence>
<xs:element name="Fred" minOccurs="0" maxOccurs="unbounded">
<xs:complexType>
<xs:sequence>
<xs:element name="Name" type="xs:string" minOccurs="0" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:choice>
</xs:complexType>
</xs:element>
</xs:schema>
我刚刚遇到了 C# 错误吗?还是我遗漏了一些明显的东西?
注意:使用两次名称不是问题,如果我创建一个名为George 的结构与Fred 相同,并将Middle 的内容更改为public George[] George,问题是好多了。
TLDR(对于撇渣器):这 post 由两部分组成。
第 1 部分:Protobuf 快速介绍。这里用到了属性。
第 2 部分:问题的实际答案:在不修改继承库的情况下配置序列化
好的,我试试看。
问题似乎是您使用的是 Compact Framework,它不具有与完整 .NET Framework 相同的 serialization/deserialization 功能。所以我们需要一些自定义序列化。
按照 Compact Framework 的理念,我的猜测是您还想要性能良好且占用空间小的东西。所以我选择了 Protobuf for the task (which is also approximately 12 times faster than XmlSerializer)
你可以通过运行这个命令来安装它:
Install-Package protobuf-net
让我们从简单的方法开始 - 通过向模型添加属性。 接下来是没有属性的配置,正如您指出的那样,原始模型不能被修改。t/shouldn。这只是为了说明。
用适当的属性装饰后,您的模型将如下所示:
第 1 部分:配置属性。
我再说一遍,这部分仅用于说明目的 - 请继续阅读 "Configuration without attributes"
[ProtoContract]
public struct Fred
{
[ProtoMember(1)]
public string Name;
}
[ProtoContract]
public struct Middle
{
[ProtoMember(1)]
public Fred[] Freds;
}
[ProtoContract]
public struct Top
{
[ProtoMember(1)]
public Middle Middle;
[ProtoMember(2)]
public Fred[] Freds;
}
这里唯一要注意的是编号成员的用法,称为键。在 JSON 或 XML 序列化的情况下,这与给它们 属性 名称本质上是一样的,除了这是 protobuf 的方法。您只需为同一 class 中的每个成员分配一个唯一的整数值,大多数情况下您就完成了。
为了方便起见,让我们添加一个简单的构建器,我们可以从中实例化一个类似于您示例中的 Top
:
public class TopTestBuilder
{
public Top BuildDefaultTestTop()
{
var top = new Top
{
Middle = new Middle
{
Freds = new[]
{
new Fred {Name = "Fred20"},
new Fred {Name = "Fred21"}
}
},
Freds = new[]
{
new Fred {Name = "Fred10"},
new Fred {Name = "Fred11"}
}
};
return top;
}
}
我们可以像这样序列化它:
Top topIn = new TopTestBuilder().BuildDefaultTestTop();
string serialized;
using (var stream = new MemoryStream())
{
Protobuf.Serializer.Serialize(stream, topIn);
stream.Position = 0;
var reader = new StreamReader(stream);
serialized = reader.ReadToEnd();
}
// Output: "\nDC4\n\b\nACKFred20\n\b\nACKFred21DC2\b\nACKFred10DC2\b\nACKFred11"
并且反序列化它是这样的:
Top topOut;
using (var stream = new MemoryStream())
{
var writer = new StreamWriter(stream);
writer.Write(serialized);
writer.Flush();
stream.Position = 0;
topOut = Protobuf.Serializer.Deserialize<Top>(stream);
}
如您所见,MemoryStreams
有一些管道,但除此之外,其他类型的序列化工作方式看起来应该很熟悉。同样,一切都可以通过配置自定义 TypeModel
来完成,从而使序列化与模型完全解耦。
第 2 部分:没有属性的配置
默认情况下,Protobuf 使用属性定义 TypeModel
,然后将其存储在 ProtoBuf.Meta.RuntimeTypeModel.Default
中。当您直接调用静态 Protobuf.Serializer
时,将使用此 属性。
我们也可以定义我们自己的。花了一些时间(自我注意:RTFM)让它工作,但结果几乎一样简单:
var model = TypeModel.Create();
// The first parameter (maps to ProtoContractAttribute) is the Type to be included.
// The second parameter determines whether to apply default behavior,
// based on the attributes. Since we're not using those, this has no effect.
model.Add(typeof(Fred), false);
model.Add(typeof(Middle), false);
model.Add(typeof(Top), false);
// The newly added MetaTypes can be accessed through their respective Type indices.
// The first parameter is the unique member number, similar to ProtoMemberAttribute.
// The second parameter is the name of the member as it is declared in the class.
// When the member is a list:
// The third parameter is the Type for the items.
// The fourth parameter is the Type for the list itself.
model[typeof(Fred)].Add(1, "Name");
model[typeof(Middle)].Add(1, "Freds", typeof(Fred), typeof(Fred[]));
model[typeof(Top)].Add(1, "Middle");
model[typeof(Top)].Add(2, "Freds", typeof(Fred), typeof(Fred[]));
现在我们所要做的就是为两个函数更改一行代码:
序列化:
Top topIn = new TopTestBuilder().BuildDefaultTestTop();
string serialized;
using (var stream = new MemoryStream())
{
model.Serialize(stream, top);
stream.Position = 0;
var reader = new StreamReader(stream);
serialized = reader.ReadToEnd();
}
反序列化:
Top topOut;
using (var stream = new MemoryStream())
{
var writer = new StreamWriter(stream);
writer.Write(serialized);
writer.Flush();
stream.Position = 0;
topOut = (Top) _model.Deserialize(stream, null, typeof (Top));
}
它的工作原理是一样的。也许添加一个 class 来保持组织 - 给它两个 public 方法 Serialize
和 Deserialize
,以及一个私有方法 BuildTypeModel
(从构造函数调用和存储在序列化程序的字段中?)
您的调用代码最终看起来像这样:
var serializer = new CustomProtoBufSerializer();
var serialized = serializer.Serialize(someClassInput);
SomeClass someClassOutput = serializer.Deserialize(serialized);
有一件事很快就变得清晰了——Protobuf 还没有像大多数 JSON 和 XML 序列化程序那样得到彻底的记录和测试。这一点,连同序列化结果对人类来说是不可读的,在某些情况下可能是一个缺点。除此之外,它看起来速度快、重量轻并且与许多不同的环境兼容。
自动类型解析的缺失让我有点困扰,所以我去寻找并发现了一些看起来很有趣的东西:Protobuf T4 TypeModel Generator。我还没来得及尝试。如果人们有兴趣,我可能会稍后再做,并用更通用的解决方案更新答案。
如果您在使用它时遇到任何问题,请告诉我。