我正在使用xml注释来记录我的组件的公共以及内部和私有成员。我想将生成的文档xml文件与组件程序集打包在一起,以便为最终产品启用“富”(例如,使用方法,异常和参数描述)Visual Studio Intellisense。它的问题是C#编译器为所有内容(包括内部类,方法,内部枚举的私有字段等)创建文档条目,并且似乎没有切换到“仅公共成员”模式。
现在我不想在每个文件中使用XX方法查看超过50个文件,并删除私人和内部成员的所有注释。即使我这样做,我可能也不会在auto-gen'd资源文件上取得太大成功,因为这些强类型资源类会自动注释而非公开。
我的问题是:是否有一些选项/标志我忽略了?如果不是,是否有一些工具可以帮助将公共成员与其他成员分开(在我开始编写代码之前)?
答案 0 :(得分:5)
SandCastle帮助文件构建器可以选择重新创建仅包含方法,属性等的已配置访问模式的xml文件...
唯一的“缺点”是您必须生成文档。
因为很久以前我忘记了为SHFB添加了一个“组件”来生成XML。
好消息是该组件包含在SHFB中。
您必须将“智能感知组件”添加到SHFB项目中。然后,它将根据配置的SHFB项目生成XML。
答案 1 :(得分:4)
eazfuscator中有一个工具可以删除非公开文档。您可以看到示例here
答案 2 :(得分:3)
我已经考虑过这个问题了,我决定改变解决这个问题的方法。而不是在程序集中查找类型/成员来尝试解析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<string>
{
private HashSet<string> stringSet = new HashSet<string>(StringComparer.Ordinal);
public XmlDocumentationStringSet(Assembly assembly)
{
AddRange(assembly.GetExportedTypes());
}
public bool Contains(string name)
{
return stringSet.Contains(name);
}
/// <summary>
/// Heelloasdasdasd
/// </summary>
/// <param name="types"></param>
public void AddRange(IEnumerable<Type> 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<string> GetEnumerator()
{
return stringSet.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}
哦,我还没弄清楚如何处理事件,在这个例子中它们总是可见的。
答案 3 :(得分:2)
@John Leidegren
我有同样的要求,而且我找到了代码遗漏的答案。事件有2个方法,添加和删除,如果其中任何一个是公共的,则被视为公共。所以它会是这样的:
private bool IsVisible(EventInfo eventInfo)
{
return eventInfo.GetAddMethod(false) != null
|| eventInfo.GetRemoveMethod(false) != null;
}
虽然我想不出为什么一个人会公开而不是另一个人的原因。
答案 4 :(得分:0)
您使用什么工具来生成文档?我使用Sandcastle,您可以选择按可访问性选择要包含的成员。
一般来说,似乎预计XML会包含可能所需的所有信息,并且可以由处理工具从中选择所需的信息。
答案 5 :(得分:0)
我遇到了同样的问题。 SHFB很慢,因为我们有另一个文档代码库,我们不需要它为我们生成文档。
我最终使用XMLStarlet加上一个单独的命名空间用于内部类。例如,我的所有内部类都将驻留在MyCompany.MyProduct.Internal
中。然后我可以使用一个简单的命令
xml ed -L -d "//member[contains(@name, 'MyCompany.MyProduct.Internal')]" MyProduct.xml
清理XML。这当然不是防弹 - 它不包括公共类中的内部成员,并且它确实需要一些纪律来记住将内部类放入内部。但这是对我有用的最干净,最不干扰的方法。它也是一个独立的EXE文件,可以轻松地检入构建服务器,没有汗水。