.NET无法反序列化嵌套结构?

时间:2015-03-30 14:24:39

标签: c# xml serialization compact-framework

我遇到了使C#(VS2008,Compact Framework,.NET是版本3.5 SP1)成功反序列化嵌套结构的问题。当我在移动设备的模拟器上运行时(我正在使用“Pocket PC 2003 Second Edition”模拟器),问题只出现在CF中,在我的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的内容更改为公共George [] George,则问题不会更好。< / p>

1 个答案:

答案 0 :(得分:1)

TLDR(对于撇油器):这篇文章由两部分组成。

第1部分:Protobuf的快速介绍。这里使用属性。

第2部分:问题的实际答案:配置序列化而不修改继承的库

好的,我会试一试。

问题似乎是您正在使用Compact Framework,它不具备与完整.NET框架相同的序列化/反序列化功能。所以我们需要一些自定义序列化。

遵循Compact Framework的理念,我的猜测是你也想要一些表现良好且占地面积小的东西。所以我选择了Protobuf来完成任务(也是approximately 12 times faster than XmlSerializer

您可以通过运行此命令来安装它:

Install-Package protobuf-net

让我们以简单的方式开始 - 通过向模型添加属性。 接下来是没有属性的配置,因为您指出原始模型不能被修改。这只是为了说明。

使用适当的属性进行修饰,您的模型将如下所示:

第1部分:具有属性的配置。

我再说一遍,这部分仅用于说明目的 - 请继续阅读&#34;没有属性的配置&#34;

[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方法。您只需为同一个班级中的每个成员分配一个唯一的整数值,并且大部分时间都是在那里完成的。

为方便起见,我们添加一个简单的构建器,我们可以从中实例化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));
}

它的工作原理相同。也许添加一个类来保持组织有序 - 给它两个公共方法SerializeDeserialize,以及一个私有方法BuildTypeModel(从构造函数调用并存储在序列化器的字段中? )

您的调用代码最终会看起来像这样:

var serializer = new CustomProtoBufSerializer();

var serialized = serializer.Serialize(someClassInput);

SomeClass someClassOutput = serializer.Deserialize(serialized);

有一件事很快就变得清晰了 - Protobuf没有像大多数JSON和XML序列化器那样经过全面记录和测试。在某些情况下,这与序列化结果对人类不可读是一个缺点。除此之外,它似乎快速,轻便且兼容许多不同的环境。

缺少自动类型分辨率让我感到困扰,所以我去寻找并找到了一些非常有趣的东西:Protobuf T4 TypeModel Generator。我还没有尝试过。如果人们对此感兴趣,我可能会在稍后使用更通用的解决方案更新答案。

如果您在使用它时遇到任何问题,请告诉我。