序列化时重命名 class 为 XML

Rename class when serializing to XML

我正在尝试序列化如下所示的 Outer class,并从序列化的 XML 创建一个 XElement。它有一个 属性 类型 Inner。我想更改 Inner(至 Inner_X)和 Outer(至 Outer_X)的名称。

class Program
{
    static void Main(string[] args)
    {
        using (MemoryStream memoryStream = new MemoryStream())
        {
            using (TextWriter streamWriter = new StreamWriter(memoryStream))
            {
                var xmlSerializer = new XmlSerializer(typeof(Outer));

                xmlSerializer.Serialize(streamWriter,  new Outer());

                XElement result = XElement.Parse(Encoding.ASCII.GetString(memoryStream.ToArray()));
            }
        }
    }
}

[XmlType("Outer_X")]
public class Outer
{
    public Outer()
    {
        this.InnerItem = new Inner();
    }

    public Inner InnerItem { get; set; }
}

[XmlType("Inner_X")]
public class Inner
{
}

这将创建一个 XElement,如下所示:

<Outer_X xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
   <InnerItem />
</Outer_X>

我想要的是:

<Outer_X xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
   <Inner_X />
</Outer_X>

我想保留有关如何使用 class 重命名 class 的信息。 我想我可以使用 XmlType 属性。但是,这将被忽略,而是使用 属性 名称。

我查看了 here and here 等其他地方,觉得这应该可行。我错过了什么?

澄清

"keep(ing) the information about how a class should be renamed with that class",我的意思是 Inner_X 应该只出现在 Inner class 中。它根本不应该出现在 Outer class.

您需要设置 属性 的元素名称,而不是内部 class 的 xml 类型。 试试这个:

[XmlType("Outer_X")]
public class Outer
{
    public Outer()
    {
        this.InnerItem = new Inner();
    }

    [XmlElement("Inner_X")]
    public Inner InnerItem { get; set; }
}


public class Inner
{
}

XmlSerializer 序列化类型时,类型本身控制为其属性创建的元素的名称。 IE。 属性 名称成为元素名称,除非被 XmlElementAttribute.ElementName. XmlTypeAttribute.TypeName 静态覆盖通常只在应用它的类型的实例是 not 时控制元素名称被序列化为某些包含类型的 属性——例如,当它是根元素时,或者当它包含在使用外部容器元素序列化的集合中时。这种设计避免了在给定类型中存在多个相同类型的属性的情况下的名称冲突。

但是,在多态属性类型的情况下有一个例外。对于这些,XmlSerializer 可以选择使用每个可能的多态类型的 XML 类型名称作为元素名称,从而识别创建元素的实际 c# 类型。要启用此功能,必须向 属性 添加多个 [XmlElement(typeof(TDerived))] 属性,每个属性对应一种可能的类型 TDerived

您可以使用此功能通过引入伪多态代理 属性:

来生成您需要的 XML
[XmlType("Outer_X")]
public class Outer
{
    public Outer()
    {
        this.InnerItem = new Inner();
    }

    [XmlIgnore]
    public Inner InnerItem { get; set; }

    [XmlElement(typeof(Inner))]
    [XmlElement(typeof(object))]
    [Browsable(false), EditorBrowsable(EditorBrowsableState.Never), DebuggerBrowsable(DebuggerBrowsableState.Never)]
    public object InnerItemXmlProxy
    {
        get
        {
            return InnerItem;
        }
        set
        {
            InnerItem = (Inner)value;
        }
    }
}

那么输出就是你所要求的:

<Outer_X xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <Inner_X />
</Outer_X>

原型fiddle.

但是,正如@evk 评论的那样,如果您的 Outer class 包含同一类型的多个属性,则无法完成此操作。

另一个需要考虑的选项:如果您只是不想在多个位置(即 [XmlType(string name)][XmlElement(string name)] 中手动复制 "Inner_X" 类型名称字符串属性)你可以通过使它们成为 public const:

来集中类型名称
[XmlType(Outer.XmlTypeName)]
public class Outer
{
    public const string XmlTypeName = "Outer_X";

    public Outer()
    {
        this.InnerItem = new Inner();
    }

    [XmlElement(Inner.XmlTypeName)]
    public Inner InnerItem { get; set; }
}

[XmlType(Inner.XmlTypeName)]
public class Inner
{
    public const string XmlTypeName = "Inner_X";
}

更新

我刚刚注意到你的评论我打算将 Inner 作为一个抽象基础 class,其中的每个子class 将序列化为不同的元素名称。如果是这种情况,那么 XmlSerializer 确实可以使用 XML 类型名称作为元素名称——但只有当它可以静态确定 属性 类型是 实际上是多态的,因为存在 多个 [XmlElement(typeof(TDerived))] 属性。因此,以下 classes 将生成您需要的 XML:

[XmlType("Outer_X")]
public class Outer
{
    public Outer()
    {
        this.InnerItem = new InnerX();
    }

    [XmlElement(typeof(InnerX))]
    [XmlElement(typeof(Inner))] // Necessary to inform the serializer of polymorphism even though Inner is abstract.
    public Inner InnerItem { get; set; }
}

public abstract class Inner
{
}

[XmlType("Inner_X")]
public class InnerX : Inner
{
}

实现起来非常简单。你需要使用XmlRootAttribute for the class and the XmlElementAttribute for the members as explained here on MSDN

[XmlRoot(ElementName = "Outer_X")]
public class Outer
{    
    [XmlElement(ElementName = "Inner_X")]
    public Inner InnerItem { get; set; } = new Inner();
}

public class Inner { }

我创建了一个有效的 .NET Fiddle to exemplify this. This SO Q & A seemed to address this similar concern. Finally, when decoding the XML to a string you should probably use a different encoding, no? According to this,字符串是 UTF-16 编码的——没什么大不了的,但我想我会引起注意。

我的fiddle分享结果如下XML:

<Outer_X xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <Inner_X />
</Outer_X>

更新

在您通过澄清更新问题后,我现在明白您要问什么了。不幸的是,(据我所知)这不能通过属性来控制。您必须创建自己的 XML 序列化器/反序列化器,或者接受属性支持存在限制的事实。