使用XmlSerializer时如何在XML文件中写注释?

Jen*_*sen 20 .net c# xml xml-serialization xmlserializer

我有一个对象Foo,我将其序列化为XML流.

public class Foo {
  // The application version, NOT the file version!
  public string Version {get;set;}
  public string Name {get;set;}
}

Foo foo = new Foo { Version = "1.0", Name = "Bar" };
XmlSerializer xmlSerializer = new XmlSerializer(foo.GetType());
Run Code Online (Sandbox Code Playgroud)

这可以快速,轻松地完成当前所需的一切.

我遇到的问题是我需要维护一个单独的文档文件,其中包含一些小的评论.如上例所示,Name很明显,但是Version应用程序版本而不是数据文件版本,正如人们在这种情况下所期望的那样.而且我还有许多类似的小事我想用评论来澄清.

我知道如果我使用该WriteComment()函数手动创建我的XML文件,我可以这样做,但是我可以实现可能的属性或替代语法,以便我可以继续使用序列化程序功能吗?

dbc*_*dbc 17

通过使用返回类型对象XmlComment并使用标记这些属性的属性,可以使用默认基础结构[XmlAnyElement("SomeUniquePropertyName")].

即如果你添加一个Foo像这样的属性:

public class Foo
{
    [XmlAnyElement("VersionComment")]
    public XmlComment VersionComment { get { return new XmlDocument().CreateComment("The application version, NOT the file version!"); } set { } }

    public string Version { get; set; }
    public string Name { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

将生成以下XML:

<Foo>
  <!--The application version, NOT the file version!-->
  <Version>1.0</Version>
  <Name>Bar</Name>
</Foo>
Run Code Online (Sandbox Code Playgroud)

但是,问题是要求更多,即在文档系统中查找注释的某种方式.以下通过使用扩展方法根据反映的注释属性名称查找文档来实现此目的:

public class Foo
{
    [XmlAnyElement("VersionXmlComment")]
    public XmlComment VersionXmlComment { get { return GetType().GetXmlComment(); } set { } }

    [XmlComment("The application version, NOT the file version!")]
    public string Version { get; set; }

    [XmlAnyElement("NameXmlComment")]
    public XmlComment NameXmlComment { get { return GetType().GetXmlComment(); } set { } }

    [XmlComment("The application name, NOT the file name!")]
    public string Name { get; set; }
}

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class XmlCommentAttribute : Attribute
{
    public XmlCommentAttribute(string value)
    {
        this.Value = value;
    }

    public string Value { get; set; }
}

public static class XmlCommentExtensions
{
    const string XmlCommentPropertyPostfix = "XmlComment";

    static XmlCommentAttribute GetXmlCommentAttribute(this Type type, string memberName)
    {
        var member = type.GetProperty(memberName);
        if (member == null)
            return null;
        var attr = member.GetCustomAttribute<XmlCommentAttribute>();
        return attr;
    }

    public static XmlComment GetXmlComment(this Type type, [CallerMemberName] string memberName = "")
    {
        var attr = GetXmlCommentAttribute(type, memberName);
        if (attr == null)
        {
            if (memberName.EndsWith(XmlCommentPropertyPostfix))
                attr = GetXmlCommentAttribute(type, memberName.Substring(0, memberName.Length - XmlCommentPropertyPostfix.Length));
        }
        if (attr == null || string.IsNullOrEmpty(attr.Value))
            return null;
        return new XmlDocument().CreateComment(attr.Value);
    }
}
Run Code Online (Sandbox Code Playgroud)

为其生成以下XML:

<Foo>
  <!--The application version, NOT the file version!-->
  <Version>1.0</Version>
  <!--The application name, NOT the file name!-->
  <Name>Bar</Name>
</Foo>
Run Code Online (Sandbox Code Playgroud)

笔记:

  • 扩展方法XmlCommentExtensions.GetXmlCommentAttribute(this Type type, string memberName)假定comment属性将被命名为xxxXmlComment这里xxx是"真正的"属性.如果是这样,它可以通过标记传入memberName属性来自动确定实际属性名称CallerMemberNameAttribute.这可以通过传入真实姓名手动覆盖.

  • 一旦知道了类型和成员名称,扩展方法就会通过搜索[XmlComment]应用于该属性的属性来查找相关注释.这可以用缓存查找替换为单独的文档文件.

  • 虽然仍然需要为xxxXmlComment可能被注释的每个属性添加属性,但这可能不如直接实现IXmlSerializable那么麻烦,这非常棘手,可能导致反序列化中的错误,并且可能需要复杂子属性的嵌套序列化.

  • 要确保每个注释位于其关联元素之前,请参阅在C#中控制序列化的顺序.

  • 对于XmlSerializer序列化属性就必须同时具有getter和setter.因此我给评论属性设置者什么都不做.

工作.Net小提琴.

  • 不错的解决方案,但我无法控制评论出现在 xml 文件中的哪一点。在我的示例中,我导出了 5 个元素。在第一个元素之前声明了注释,但在 xml 文件中它显示在第四个元素之后。 (2认同)

Kir*_*huk 13

无法使用默认基础架构.您需要IXmlSerializable为您的目的实施.

非常简单的实现:

public class Foo : IXmlSerializable
{
    [XmlComment(Value = "The application version, NOT the file version!")]
    public string Version { get; set; }
    public string Name { get; set; }


    public void WriteXml(XmlWriter writer)
    {
        var properties = GetType().GetProperties();

        foreach (var propertyInfo in properties)
        {
            if (propertyInfo.IsDefined(typeof(XmlCommentAttribute), false))
            {
                writer.WriteComment(
                    propertyInfo.GetCustomAttributes(typeof(XmlCommentAttribute), false)
                        .Cast<XmlCommentAttribute>().Single().Value);
            }

            writer.WriteElementString(propertyInfo.Name, propertyInfo.GetValue(this, null).ToString());
        }
    }
    public XmlSchema GetSchema()
    {
        throw new NotImplementedException();
    }

    public void ReadXml(XmlReader reader)
    {
        throw new NotImplementedException();
    }
}

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class XmlCommentAttribute : Attribute
{
    public string Value { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

输出:

<?xml version="1.0" encoding="utf-16"?>
<Foo>
  <!--The application version, NOT the file version!-->
  <Version>1.2</Version>
  <Name>A</Name>
</Foo>
Run Code Online (Sandbox Code Playgroud)

另一种方式,可能更可取:使用默认序列化器序列化,然后执行后处理,即更新XML,例如使用XDocumentXmlDocument.

  • @Igor,OP可以将注释放在自定义属性中,并使用反射读取它们. (2认同)