【问题标题】:Rename class when serializing to XML序列化为 XML 时重命名类
【发布时间】:2016-04-20 13:19:27
【问题描述】:

我正在尝试序列化下面显示的 Outer 类,并从序列化的 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>

我想保留有关如何使用该类重命名类的信息。我想我可以使用XmlType 属性来做到这一点。但是,这会被忽略,而是使用属性名称。

我看过herehere 等其他地方,感觉应该可以。我错过了什么?

澄清

“保留(ing)有关如何使用该类重命名类的信息”,我的意思是术语Inner_X 应该只出现在Inner 类中。它根本不应该出现在Outer 类中。

【问题讨论】:

  • 很奇怪的要求。如果您有许多 Inner 类型的属性都具有不同的名称(InnerItem1、InnerItem2 等)怎么办?它们都应该使用相同的名称进行序列化吗?
  • 感谢@Evk 的评论。对于我真正想做的事情,我永远不会有你描述的结构。
  • 您很可能无法使用标准 XmlSerializer 来执行此操作,因为正如我所说的那样,这是一个奇怪且不寻常的要求,因此 XmlSerializer 类的开发人员很可能无法为此计划:)
  • @Evk、this answerthis answer 在我提到的问题中确实表明这应该可行。据我所知,我正在做同样的事情。有什么不同的想法吗?
  • 序列化时,有一些根对象(或根对象的数组\列表)。在该根对象上 XmlType 将起作用。但是对于子对象,它不会有任何影响,因为对于子对象,属性名称被用作元素名称(这是合理的)。或者您可以使用 XmlElement 属性覆盖属性名称,如下所示。在您的两个示例中,XmlType 仅适用于根对象。注意 - 我的意思是根不是 xml 意义上的,但在某种意义上它是您正在序列化的对象的顶级对象\列表。

标签: c# xml-serialization xmlserializer


【解决方案1】:

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

但是,多态属性类型有一个例外。对于这些,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 类包含多个相同类型的属性,则无法这样做。

另一个需要考虑的选项:如果您只是不想在多个位置手动复制 "Inner_X" 类型名称字符串(即在 [XmlType(string name)][XmlElement(string name)] 属性中),您可以集中类型名称通过让他们成为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 作为一个抽象基类,它的每个子类都将序列化为不同的元素名称。如果是这种情况,那么 XmlSerializer 确实可以使用 XML 类型名称作为元素名称——但只有当它可以静态确定属性类型 实际上是多态的多个 [XmlElement(typeof(TDerived))] 属性的存在。因此,以下类将生成您需要的 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
{
}

【讨论】:

  • 有什么方法可以根据属性名称生成动态名称? (即:而不是让 [XmlElement("description")] 来装饰名为 Descriptio 的属性,而是拥有一些动态代码,比如 CurrentName.ToLowerCase() )和 Description 将是描述?
【解决方案2】:

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

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

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


public class Inner
{
}

【讨论】:

  • 感谢您的回答,但这就是我要避免的。我想保留有关如何使用 Outer 类重命名 Outer 的信息(因为实际上,我希望 Inner 成为一个抽象基类,它的每个子类都将序列化为不同的元素名称)。我提到的其他问题似乎表明这是可能的。
  • 我已经尝试使用XmlType,正如您链接到的问题的答案中所建议的那样,但没有设法让它更改 xml 输出中的属性名称。也许其他人可能会找到可行的解决方案。
【解决方案3】:

这实现起来非常简单。您需要将XmlRootAttribute 用于class,将XmlElementAttribute 用于成员,如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 来举例说明这一点。这个SO Q & A 似乎解决了这个类似的问题。最后,在将 XML 解码为字符串时,您可能应该使用不同的编码,不是吗?根据this 的说法,字符串是 UTF-16 编码的——没什么大不了的,但我想我会引起注意。

我分享的小提琴导致以下 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 序列化器/反序列化器,要么接受属性支持存在限制的事实。

【讨论】:

  • 感谢您的回答,但这就是我要避免的。我已经更新了我的问题以澄清我的要求。我提到了你在我的问题中提到的问答,因为那里的答案之一表明我所做的应该有效。但是,我无法重现它。
猜你喜欢
  • 2021-01-06
  • 2015-08-25
  • 2019-12-15
  • 2011-01-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-03-13
相关资源
最近更新 更多