C# XMLSerializer 将错误的类型反序列化为 List

Posted

技术标签:

【中文标题】C# XMLSerializer 将错误的类型反序列化为 List【英文标题】:C# XMLSerializer is deserializing the wrong type into a List 【发布时间】:2017-08-22 15:26:40 【问题描述】:

下面的程序是我在 C# 中反序列化 XML 时发现的一个问题的人为示例。我有两个单独的程序集,它们声明了一个具有相同名称的类型,在下面的示例中为“国家/地区”。这些类型由 XML 命名空间区分。当我反序列化包含单个“国家/地区”元素的配置文件时,会解析正确的“国家/地区”类型。但是,如果我反序列化“国家”元素的“列表”,则反序列化错误的“国家”类型。

class Program

    static void Main(string[] args)
    
        XDocument gbConfig = XDocument.Parse(@"<TradingBlocConfiguration>
                                                 <GreatBritain>
                                                   <Country/>
                                                   <Countries>
                                                      <Country/>
                                                      <Country/>                                                                        
                                                    </Countries>
                                                  </GreatBritain>                                                                     </TradingBlocConfiguration>");


        XDocument euConfig = XDocument.Parse(@"<TradingBlocConfiguration>
                                                 <EuropeanUnion>
                                                   <Country/>
                                                   <Countries>
                                                      <Country/>
                                                      <Country/>                                                                        
                                                    </Countries>
                                                  </EuropeanUnion>                                                                     </TradingBlocConfiguration>");

        var greatBritainConfiguration = BuildConfig<TradingBlocConfiguration>(gbConfig);

        // A single 'Country' is always deserialized correctly..
        Console.WriteLine("Great Britain Country Type   " + greatBritainConfiguration.TradingBlocConfig.MemberCountry.GetType());

        // A List of 'Country' is deserialized to the wrong type, depending on what '[XmlElement]' tag is listed first.
        Console.WriteLine("Great Britain Countries Type " + greatBritainConfiguration.TradingBlocConfig.MemberCountries[0].GetType());

        var euConfiguration = BuildConfig<TradingBlocConfiguration>(euConfig);
        Console.WriteLine("EU Country Type              " + euConfiguration.TradingBlocConfig.MemberCountry.GetType());
        Console.WriteLine("EU Countries Type            " + euConfiguration.TradingBlocConfig.MemberCountries[0].GetType());

        Console.ReadLine();
    

    private static T BuildConfig<T>(XDocument doc) where T : class
    
        var stream = new MemoryStream();
        doc.Save(stream);     

        T result;
        using (var reader = new StreamReader(stream))
        
            stream.Position = 0;
            var xs = new XmlSerializer(typeof(T));
            result = (T)xs.Deserialize(reader);
        

        return result;
    


[XmlRoot("TradingBlocConfiguration")]
public sealed class TradingBlocConfiguration

    [XmlElement("GreatBritain", typeof(GB.GreatBritain))]
    [XmlElement("EuropeanUnion", typeof(EU.EuropeanUnion))]
    public TradingBloc TradingBlocConfig  get; set;         


[XmlRoot]
[XmlInclude(typeof(GB.GreatBritain))]
[XmlInclude(typeof(EU.EuropeanUnion))]
public class BaseCountry  

public abstract class TradingBloc

    [XmlIgnore]
    public abstract List<BaseCountry> MemberCountries  get; set; 

    [XmlIgnore]
    public abstract BaseCountry MemberCountry  get; set; 


namespace GB
       
    [XmlRoot("GreatBritain")]
    public class GreatBritain : TradingBloc
    
        [XmlElement("Country", typeof(Country))]
        public override BaseCountry MemberCountry  get; set; 

        [XmlArray("Countries")]
        [XmlArrayItem("Country", typeof(Country))]
        public override List<BaseCountry> MemberCountries  get; set; 

        [XmlRoot(Namespace = "GB")]
        public class Country : BaseCountry  
    


namespace EU
        
    [XmlRoot("EuropeanUnion")]
    public class EuropeanUnion : TradingBloc
    
        [XmlElement("Country", typeof(Country))]
        public override BaseCountry MemberCountry  get; set; 

        [XmlArray("Countries")]
        [XmlArrayItem("Country", typeof(Country))]
        public override List<BaseCountry> MemberCountries  get; set; 

        [XmlRoot(Namespace = "EU")]
        public class Country : BaseCountry  
    

如果你运行上面的例子,输出是:

Great Britain Country Type   XmlSerializationTests.GB.GreatBritain+Country
Great Britain Countries Type XmlSerializationTests.EU.EuropeanUnion+Country
EU Country Type              XmlSerializationTests.EU.EuropeanUnion+Country
EU Countries Type            XmlSerializationTests.EU.EuropeanUnion+Country

“英国国家类型”不正确。如果您在 TradingBlocConfiguration 类中更改 [XmlElement] 属性的顺序,例如:

[XmlRoot("TradingBlocConfiguration")]
public sealed class TradingBlocConfiguration
        
    [XmlElement("EuropeanUnion", typeof(EU.EuropeanUnion))]
    [XmlElement("GreatBritain", typeof(GB.GreatBritain))]
    public TradingBloc TradingBlocConfig  get; set; 

然后结果变为:

Great Britain Country Type   XmlSerializationTests.GB.GreatBritain+Country
Great Britain Countries Type XmlSerializationTests.GB.GreatBritain+Country
EU Country Type              XmlSerializationTests.EU.EuropeanUnion+Country
EU Countries Type            XmlSerializationTests.GB.GreatBritain+Country

在这种情况下,英国看起来不错,但欧盟错了 :)。谁能解释为什么 List 被反序列化为错误的类型?

【问题讨论】:

您可以将 XmlArray 替换为 XmlElement。 XmlArray 创建了一个额外的标签层。使用 XmlArray 你有 。使用 XmlElement 你得到 我需要 XmlArray。这是一个人为的例子。实际上,我所拥有的只是列表,我无法更改。 它仍然是一个列表/数组,只是取决于生成的标签数量。 XmlElement 使用一个标签,而 XmlArray 使用两个标签。 不幸的是,您的类型没有按名称空间区分-它们都使用默认名称空间。 XmlRoot 中指定的命名空间仅在该元素是根元素时使用。这对我来说似乎是一个错误 - 它应该在创建序列化程序时抛出异常,或者它应该工作。我只能建议手动实现IXmlSerializable 谢谢查尔斯。关于为什么它适用于单个项目的任何想法?我可以通过将 Type[] 传递给 xmlserializer 构造函数来强制它工作(比如 ..."new XmlSerializer(typeof(T), new Type[] typeof(GB.GreatBritain.Country) )" 所以我可能是能够通过提前找出正确的类型来实施解决方案。 【参考方案1】:

解决方案是添加 xmlns 标签。下面更新的代码可以正常工作:

class Program

    static void Main(string[] args)
    
        XDocument gbConfig = XDocument.Parse(@"<TradingBlocConfiguration>
                                             <GreatBritain xmlns=""GB"">
                                               <Country/>
                                               <Countries>
                                                  <Country/>
                                                  <Country/>                                                                        
                                                </Countries>
                                              </GreatBritain>                                                                     </TradingBlocConfiguration>");


        XDocument euConfig = XDocument.Parse(@"<TradingBlocConfiguration>
                                             <EuropeanUnion xmlns=""EU"">
                                               <Country/>
                                               <Countries>
                                                  <Country/>
                                                  <Country/>                                                                        
                                                </Countries>
                                              </EuropeanUnion>                                                                     </TradingBlocConfiguration>");

        var greatBritainConfiguration = BuildConfig<TradingBlocConfiguration>(gbConfig);

        // A single 'Country' is always deserialized correctly..
        Console.WriteLine("Great Britain Country Type   " + greatBritainConfiguration.TradingBlocConfig.MemberCountry.GetType());

        // A List of 'Country' is deserialized to the wrong type, depending on what '[XmlElement]' tag is listed first.
        Console.WriteLine("Great Britain Countries Type " + greatBritainConfiguration.TradingBlocConfig.MemberCountries[0].GetType());

        var euConfiguration = BuildConfig<TradingBlocConfiguration>(euConfig);
        Console.WriteLine("EU Country Type              " + euConfiguration.TradingBlocConfig.MemberCountry.GetType());
        Console.WriteLine("EU Countries Type            " + euConfiguration.TradingBlocConfig.MemberCountries[0].GetType());

        Console.ReadLine();
    

    private static T BuildConfig<T>(XDocument doc) where T : class
    
        var stream = new MemoryStream();
        doc.Save(stream);

        T result;
        using (var reader = new StreamReader(stream))
        
            stream.Position = 0;
            var xs = new XmlSerializer(typeof(T), new Type[]  typeof(GB.GreatBritain.Country) );
            result = (T)xs.Deserialize(reader);
        

        return result;
    


[XmlRoot("TradingBlocConfiguration")]
public sealed class TradingBlocConfiguration

    [XmlElement("GreatBritain", typeof(GB.GreatBritain), Namespace = "GB")]
    [XmlElement("EuropeanUnion", typeof(EU.EuropeanUnion), Namespace = "EU")]
    public TradingBloc TradingBlocConfig  get; set; 


[XmlRoot]
[XmlInclude(typeof(GB.GreatBritain))]
[XmlInclude(typeof(EU.EuropeanUnion))]
public class BaseCountry  

public abstract class TradingBloc

    [XmlIgnore]
    public abstract List<BaseCountry> MemberCountries  get; set; 

    [XmlIgnore]
    public abstract BaseCountry MemberCountry  get; set; 


namespace GB

    [XmlRoot("GreatBritain")]
    public class GreatBritain : TradingBloc
    
        [XmlElement("Country", typeof(Country))]
        public override BaseCountry MemberCountry  get; set; 

        [XmlArray("Countries")]
        [XmlArrayItem("Country", typeof(Country))]

        public override List<BaseCountry> MemberCountries  get; set; 

        [XmlRoot(Namespace = "GB")]
        public class Country : BaseCountry  
    


namespace EU

    [XmlRoot("EuropeanUnion")]
    public class EuropeanUnion : TradingBloc
    
        [XmlElement("Country", typeof(Country))]
        public override BaseCountry MemberCountry  get; set; 

        [XmlArray("Countries")]
        [XmlArrayItem("Country", typeof(Country))]
        public override List<BaseCountry> MemberCountries  get; set; 

        [XmlRoot(Namespace = "EU")]
        public class Country : BaseCountry  
    

【讨论】:

以上是关于C# XMLSerializer 将错误的类型反序列化为 List的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 XmlSerializer 类对象将具有相同名称的 2 个子元素组合成单个属性是 C#

XMLSerializer 异常“存在错误反映字段”和“对于非数组类型,您可以使用以下属性:XmlAttribute,..”

使用 XmlSerializer 将 XML 反序列化为类型

使用 XmlSerializer 将空 xml 属性值反序列化为可为空的 int 属性

C#语言中的XmlSerializer类的Serialize(Stream,Object)方法举例详解

XmlSerializer C# - 对象传递的简单值被转换并显示为 xml 文件中的科学值