仅为公共成员提取xml注释

我正在使用xml注释来记录我的组件的公共以及内部和私有成员。 我想将生成的文档xml文件与组件程序集打包在一起,以便为最终产品启用“丰富”(例如,使用方法,exception和参数描述)Visual Studio Intellisense。 它的问题是C#编译器为所有内容(包括内部类,方法,内部枚举的私有字段等)创建文档条目,并且似乎没有切换到“仅公共成员”模式。

现在我不想在每个文件中使用XX方法查看超过50个文件,并删除私人和内部成员的所有注释。 即使我这样做,我可能也不会在auto-gen’d资源文件上取得太大成功,因为这些强类型资源类会自动注释而非公开。

我的问题是:是否有一些选项/标志我忽略了? 如果不是,是否有一些工具可以帮助将公共成员与其他成员分开(在我开始编写代码之前)?

SandCastle帮助文件构建器可以选择重新创建仅包含方法,属性等的已配置访问模式的xml文件…

唯一的“缺点”是你必须生成一个文档。

编辑

因为很久以前我忘记了为SHFB添加了一个“组件”来生成XML。

好消息是该组件包含在SHFB中。

您必须将“Intellisense Component”添加到SHFB项目中。 然后,它将根据配置的SHFB项目生成XML。

有关更多信息: SHFB中的Intellisense组件

eazfuscator中有一个工具可以删除非公开文档。 你可以在这里看到一个例子

我已经考虑过这个问题了,我决定改变解决这个问题的方法。 而不是在程序集中查找类型/成员试图解析XML文档表示法。 我决定简单地为公共API构建一个字符串集(XML文档符号),然后可以用它来测试成员不公开。

这很简单。 将程序集发送到XmlDocumentationStringSet ,它将构建公共API的字符串集并删除不公开的元素。

 static void Main(string[] args) { var el = XElement.Load("ConsoleApplication18.XML"); // obviously, improve this if necessary (might not work like this if DLL isn't already loaded) // you can use file paths var assemblyName = el.Descendants("assembly").FirstOrDefault(); var assembly = Assembly.ReflectionOnlyLoad(assemblyName.Value); var stringSet = new XmlDocumentationStringSet(assembly); foreach (var member in el.Descendants("member").ToList()) // .ToList enables removing while traversing { var attr = member.Attribute("name"); if (attr == null) { continue; } if (!stringSet.Contains(attr.Value)) { member.Remove(); } } el.Save("ConsoleApplication18-public.XML"); } 

这里是构建XML文档名称的类(它有点大,但我认为无论如何我都在这里发布了整个源代码):

 using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text; namespace ConsoleApplication18 { public class XmlDocumentationStringSet : IEnumerable { private HashSet stringSet = new HashSet(StringComparer.Ordinal); public XmlDocumentationStringSet(Assembly assembly) { AddRange(assembly.GetExportedTypes()); } public bool Contains(string name) { return stringSet.Contains(name); } ///  /// Heelloasdasdasd ///  ///  public void AddRange(IEnumerable types) { foreach (var type in types) { Add(type); } } public void Add(Type type) { // Public API only if (!type.IsVisible) { return; } var members = type.GetMembers(BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public); foreach (var member in members) { Add(type, member); } } StringBuilder sb = new StringBuilder(); private void Add(Type type, MemberInfo member) { Type nestedType = null; sb.Length = 0; switch (member.MemberType) { case MemberTypes.Constructor: sb.Append("M:"); AppendConstructor(sb, (ConstructorInfo)member); break; case MemberTypes.Event: sb.Append("E:"); AppendEvent(sb, (EventInfo)member); break; case MemberTypes.Field: sb.Append("F:"); AppendField(sb, (FieldInfo)member); break; case MemberTypes.Method: sb.Append("M:"); AppendMethod(sb, (MethodInfo)member); break; case MemberTypes.NestedType: nestedType = (Type)member; if (IsVisible(nestedType)) { sb.Append("T:"); AppendNestedType(sb, (Type)member); } break; case MemberTypes.Property: sb.Append("P:"); AppendProperty(sb, (PropertyInfo)member); break; } if (sb.Length > 0) { stringSet.Add(sb.ToString()); } if (nestedType != null) { Add(nestedType); } } private bool IsVisible(Type nestedType) { return nestedType.IsVisible; } private void AppendProperty(StringBuilder sb, PropertyInfo propertyInfo) { if (!IsVisible(propertyInfo)) { sb.Length = 0; return; } AppendType(sb, propertyInfo.DeclaringType); sb.Append('.').Append(propertyInfo.Name); } private bool IsVisible(PropertyInfo propertyInfo) { var getter = propertyInfo.GetGetMethod(); var setter = propertyInfo.GetSetMethod(); return (getter != null && IsVisible(getter)) || (setter != null && IsVisible(setter)); } private void AppendNestedType(StringBuilder sb, Type type) { AppendType(sb, type.DeclaringType); } private void AppendMethod(StringBuilder sb, MethodInfo methodInfo) { if (!IsVisible(methodInfo) || (methodInfo.IsHideBySig && methodInfo.IsSpecialName)) { sb.Length = 0; return; } AppendType(sb, methodInfo.DeclaringType); sb.Append('.').Append(methodInfo.Name); AppendParameters(sb, methodInfo.GetParameters()); } private bool IsVisible(MethodInfo methodInfo) { return methodInfo.IsFamily || methodInfo.IsPublic; } private void AppendParameters(StringBuilder sb, ParameterInfo[] parameterInfo) { if (parameterInfo.Length == 0) { return; } sb.Append('('); for (int i = 0; i < parameterInfo.Length; i++) { if (i > 0) { sb.Append(','); } var p = parameterInfo[i]; AppendType(sb, p.ParameterType); } sb.Append(')'); } private void AppendField(StringBuilder sb, FieldInfo fieldInfo) { if (!IsVisible(fieldInfo)) { sb.Length = 0; return; } AppendType(sb, fieldInfo.DeclaringType); sb.Append('.').Append(fieldInfo.Name); } private bool IsVisible(FieldInfo fieldInfo) { return fieldInfo.IsFamily || fieldInfo.IsPublic; } private void AppendEvent(StringBuilder sb, EventInfo eventInfo) { if (!IsVisible(eventInfo)) { sb.Length = 0; return; } AppendType(sb, eventInfo.DeclaringType); sb.Append('.').Append(eventInfo.Name); } private bool IsVisible(EventInfo eventInfo) { return true; // hu? } private void AppendConstructor(StringBuilder sb, ConstructorInfo constructorInfo) { if (!IsVisible(constructorInfo)) { sb.Length = 0; return; } AppendType(sb, constructorInfo.DeclaringType); sb.Append('.').Append("#ctor"); AppendParameters(sb, constructorInfo.GetParameters()); } private bool IsVisible(ConstructorInfo constructorInfo) { return constructorInfo.IsFamily || constructorInfo.IsPublic; } private void AppendType(StringBuilder sb, Type type) { if (type.DeclaringType != null) { AppendType(sb, type.DeclaringType); sb.Append('.'); } else if (!string.IsNullOrEmpty(type.Namespace)) { sb.Append(type.Namespace); sb.Append('.'); } sb.Append(type.Name); if (type.IsGenericType && !type.IsGenericTypeDefinition) { // Remove "`1" suffix from type name while (char.IsDigit(sb[sb.Length - 1])) sb.Length--; sb.Length--; { var args = type.GetGenericArguments(); sb.Append('{'); for (int i = 0; i < args.Length; i++) { if (i > 0) { sb.Append(','); } AppendType(sb, args[i]); } sb.Append('}'); } } } public IEnumerator GetEnumerator() { return stringSet.GetEnumerator(); } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); } } } 

哦,我还没有弄清楚如何处理事件,它们在这个例子中总是可见的。

@John Leidegren

我有相同的要求,我找到了代码遗漏的答案。 事件有2个方法,添加和删除,如果其中任何一个是公共的,则被视为公共。 所以它会是这样的:

 private bool IsVisible(EventInfo eventInfo) { return eventInfo.GetAddMethod(false) != null || eventInfo.GetRemoveMethod(false) != null; } 

虽然我想不出为什么一个人会公开而不是另一个人的任何理由。

您使用什么工具来生成文档? 我使用Sandcastle,您可以选择按可访问性选择要包含的成员。

通常,似乎期望XML将包含可能需要的所有信息,并且由处理工具来选择所需的信息。

我遇到了同样的问题。 SHFB很慢,因为我们有另一个文档代码库,我们不需要它为我们生成文档。

我最终使用XMLStarlet加上一个单独的命名空间用于内部类。 例如,我的所有内部类都将驻留在MyCompany.MyProduct.Internal 。 然后我可以使用一个简单的命令

 xml ed -L -d "//member[contains(@name, 'MyCompany.MyProduct.Internal')]" MyProduct.xml 

清理XML。 这当然不是防弹 – 它不包括公共类中的内部成员,它确实需要一些纪律来记住将内部类放入内部。 但这是对我有用的最干净,最不干扰的方法。 它也是一个独立的EXE文件,可以轻松检入构建服务器,没有汗水。