How to write a comment to an XML file when using the XmlSerializer?

Isn't possible using default infrastructure. You need to implement IXmlSerializable for your purposes.

Very simple implementation:

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; }
}

Output:

<?xml version="1.0" encoding="utf-16"?>
<Foo>
  <!--The application version, NOT the file version!-->
  <Version>1.2</Version>
  <Name>A</Name>
</Foo>

Another way, maybe preferable: serialize with default serializer, then perform post-processing, i.e. update XML, e.g. using XDocument or XmlDocument.


Add comment at the end of xml after serialization (magic is to flush xmlWriter).

byte[] buffer;

XmlSerializer serializer = new XmlSerializer(result.GetType());

var settings = new XmlWriterSettings() { Encoding = Encoding.UTF8 };

using (MemoryStream memoryStream = new MemoryStream())
{
    using (XmlWriter xmlWriter = XmlWriter.Create(memoryStream, settings))
    {
        serializer.Serialize(xmlWriter, result);

        xmlWriter.WriteComment("test");

        xmlWriter.Flush();

        buffer = memoryStream.ToArray();
    }
}

This is possible using the default infrastructure by making use of properties that return an object of type XmlComment and marking those properties with [XmlAnyElement("SomeUniquePropertyName")].

I.e. if you add a property to Foo like this:

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; }
}

The following XML will be generated:

<Foo>
  <!--The application version, NOT the file version!-->
  <Version>1.0</Version>
  <Name>Bar</Name>
</Foo>

However, the question is asking for more than this, namely some way to look up the comment in a documentation system. The following accomplishes this by using extension methods to look up the documentation based on the reflected comment property name:

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);
    }
}

For which the following XML is generated:

<Foo>
  <!--The application version, NOT the file version!-->
  <Version>1.0</Version>
  <!--The application name, NOT the file name!-->
  <Name>Bar</Name>
</Foo>

Notes:

  • The extension method XmlCommentExtensions.GetXmlCommentAttribute(this Type type, string memberName) assumes that the comment property will be named xxxXmlComment where xxx is the "real" property. If so, it can automatically determine the real property name by marking the incoming memberName attribute with CallerMemberNameAttribute. This can be overridden manually by passing in the real name.

  • Once the type and member name are known, the extension method looks up the relevant comment by searching for an [XmlComment] attribute applied to the property. This could be replaced with a cached lookup into a separate documentation file.

  • While it is still necessary to add the xxxXmlComment properties for each property that might be commented, this is likely to be less burdensome than implementing IXmlSerializable directly which is quite tricky, can lead to bugs in deserialization, and can require nested serialization of complex child properties.

  • To ensure that each comment precedes its associated element, see Controlling order of serialization in C#.

  • For XmlSerializer to serialize a property it must have both a getter and setter. Thus I gave the comment properties setters that do nothing.

Working .Net fiddle.