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

JMc*_*JMc 5 c# xml serialization xml-serialization xmlserializer

下面的程序是我在C#中反序列化XML时发现的一个问题的例子.我有两个单独的程序集,它们声明一个具有相同名称的类型,在下面的示例中为"Country".这些类型由XML命名空间区分.当我反序列化包含单个"Country"元素的配置文件时,将解析正确的"Country"类型.但是,如果我反序列化"Country"元素的"List",则反序列化错误的"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 { }
    }
}
Run Code Online (Sandbox Code Playgroud)

如果您运行上面的示例,则输出为:

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
Run Code Online (Sandbox Code Playgroud)

"英国国家类型"不正确.如果您更改TradingBlocConfiguration类中[XmlElement]属性的顺序,如:

[XmlRoot("TradingBlocConfiguration")]
public sealed class TradingBlocConfiguration
{        
    [XmlElement("EuropeanUnion", typeof(EU.EuropeanUnion))]
    [XmlElement("GreatBritain", typeof(GB.GreatBritain))]
    public TradingBloc TradingBlocConfig { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

然后结果变为:

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
Run Code Online (Sandbox Code Playgroud)

在这种情况下,英国看起来不错,但欧盟错了:).任何人都可以解释为什么List被反序列化为错误的类型?

JMc*_*JMc 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 { }
    }
}
Run Code Online (Sandbox Code Playgroud)