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

5
下面的程序是我在C#中反序列化XML时发现的一个问题的虚构示例。我有两个不同的程序集,它们声明了一个具有相同名称"Country"的类型,如下例所示。这些类型通过XML命名空间进行区分。当我反序列化包含单个"Country"元素的配置文件时,正确的"Country"类型被解析。但是,如果我反序列化一个"Country"元素的"列表",则会反序列化错误的"Country"类型。
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

在这种情况下,英国看起来很好,但欧盟错了 :)。有人能解释一下为什么列表反序列化成了错误的类型吗?

你可以用 XmlElement 替换 XmlArray。XmlArray 会创建额外的标签层级。使用 XmlArray 会得到 <A><B><C><C><C></B></A> 的结果。而使用 XmlElement 则会得到 <A><C><C><C></A> 的结果。 - jdweng
我需要XmlArray。这只是一个人为的例子。实际上,我只有List,而且我不能改变它。 - JMc
它仍然是一个列表/数组,只是取决于生成的标签数量。XmlElement 使用一个标签,而 XmlArray 使用两个标签。 - jdweng
2
很不幸,您的类型没有通过命名空间进行区分 - 它们都使用默认命名空间。在XmlRoot中指定的命名空间仅在该元素是根元素时使用。这对我来说看起来像是一个错误 - 要么在创建序列化程序时应该抛出异常,要么就应该正常工作。我只能建议手动实现IXmlSerializable - Charles Mager
谢谢,查尔斯。你觉得为什么单个项可以正常工作呢?我可以通过将Type[]传递给xmlserializer构造函数来强制它工作(例如..."new XmlSerializer(typeof(T), new Type[] { typeof(GB.GreatBritain.Country) })"),因此我可以尝试预先确定正确的类型来实现一个解决方案。 - JMc
根据您的评论“XmlRoot仅在该元素是根元素时使用”,我使用命名空间解决了问题。我在XML本身中添加了“xmlns =” ,即<GreatBritain xmlns =“”GB“”>和<EuropeanUnion xmlns =“”EU“”>。然后在'TradingBlocConfiguration'中,我为每个XML标记添加了'Namespace = ...'。例如 - [XmlElement(“GreatBritain”,typeof(GB.GreatBritain),Namespace =“GB”)]和[XmlElement(“EuropeanUnion”,typeof(EU.EuropeanUnion),Namespace =“EU”)]。 - JMc
1个回答

0
解决方案是添加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 { }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接