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

我有一个对象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()); 

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

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

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

无法使用默认基础架构。 您需要为您的目的实现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().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; } } 

输出:

    1.2 A  

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

通过使用返回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; } } 

将生成以下XML:

   1.0 Bar  

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

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

为其生成以下XML:

   1.0  Bar  

笔记:

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

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

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

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

  • 要使XmlSerializer序列化属性,它必须同时具有getter和setter。 因此,我给评论属性设置者什么都不做。

工作.Net小提琴 。

可能迟到了,但是当我尝试使用Kirill Polishchuk解决方案进行反序列化时,我遇到了问题。 最后,我决定在序列化后编辑XML,解决方案如下:

 public static void WriteXml(object objectToSerialize, string path) { try { using (var w = new XmlTextWriter(path, null)) { w.Formatting = Formatting.Indented; var serializer = new XmlSerializer(objectToSerialize.GetType()); serializer.Serialize(w, objectToSerialize); } WriteComments(objectToSerialize, path); } catch (Exception e) { throw new Exception($"Could not save xml to path {path}. Details: {e}"); } } public static T ReadXml(string path) where T:class, new() { if (!File.Exists(path)) return null; try { using (TextReader r = new StreamReader(path)) { var deserializer = new XmlSerializer(typeof(T)); var structure = (T)deserializer.Deserialize(r); return structure; } } catch (Exception e) { throw new Exception($"Could not open and read file from path {path}. Details: {e}"); } } private static void WriteComments(object objectToSerialize, string path) { try { var propertyComments = GetPropertiesAndComments(objectToSerialize); if (!propertyComments.Any()) return; var doc = new XmlDocument(); doc.Load(path); var parent = doc.SelectSingleNode(objectToSerialize.GetType().Name); if (parent == null) return; var childNodes = parent.ChildNodes.Cast().Where(n => propertyComments.ContainsKey(n.Name)); foreach (var child in childNodes) { parent.InsertBefore(doc.CreateComment(propertyComments[child.Name]), child); } doc.Save(path); } catch (Exception) { // ignored } } private static Dictionary GetPropertiesAndComments(object objectToSerialize) { var propertyComments = objectToSerialize.GetType().GetProperties() .Where(p => p.GetCustomAttributes(typeof(XmlCommentAttribute), false).Any()) .Select(v => new { v.Name, ((XmlCommentAttribute) v.GetCustomAttributes(typeof(XmlCommentAttribute), false)[0]).Value }) .ToDictionary(t => t.Name, t => t.Value); return propertyComments; } [AttributeUsage(AttributeTargets.Property)] public class XmlCommentAttribute : Attribute { public string Value { get; set; } }