Извлечь XML-комментарии только для публичных пользователей
Я использую комментарии xml для публикации документов, а также для внутренних и частных членов моих компонентов. Я хотел бы упаковать сгенерированные XML файлы документации с сборками компонентов, чтобы включить "богатый" (например, с помощью метода, исключения и описания параметров) Visual Studio Intellisense с конечным продуктом. Проблема заключается в том, что компилятор С# создает записи документации для всех (включая внутренние классы, методы, частные поля внутренних перечислений и т.д.), И, похоже, нет переключения на режим "только публичные члены".
Теперь я не хочу пересылать более 50 файлов с помощью XX методов в каждом и удалять все комментарии для частных и внутренних членов. Даже если бы я это сделал, я, вероятно, не имел бы большого успеха с файлами ресурсов auto-gen'd, потому что эти строго типизированные классы ресурсов автоматически комментируются и непубличны.
Мой вопрос: есть ли какой-то параметр/флаг, который я пропускаю? Если нет, есть ли какие-то инструменты, которые могли бы помочь отделить публичных членов от остальных (до того, как я начну кодировать один)?
Ответы
Ответ 1
SandCastle Help File Builder имеет возможность воссоздать файлы xml, содержащие только настроенные режимы доступа для методов, свойств и т.д.
Единственным недостатком является то, что вам придется создавать документацию.
ИЗМЕНИТЬ
Поскольку я давно забыл, что я добавил "компонент" в SHFB для генерации XML.
Хорошей новостью является то, что этот компонент включен в SHFB.
Вы должны добавить "компонент Intellisense" в проект SHFB. Затем он сгенерирует XML в соответствии с сконфигурированным проектом SHFB.
Для получения дополнительной информации: Компонент Intellisense в SHFB
Ответ 2
В eazfuscator есть инструмент, который может удалить непубличную документацию. Вы можете увидеть пример здесь
Ответ 3
Я подумал об этом и решил изменить способ решения этой конкретной проблемы. Вместо того, чтобы находить тип/член в сборке, пытаясь разобрать нотацию документации XML. Я решил просто создать набор строк (нотации документации XML) для общедоступного API, который затем можно использовать для того, чтобы член не был общедоступным.
Это действительно просто. Отправьте сборку в 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();
}
}
}
О, и я еще не понял, как обрабатывать события, они всегда видны в этом примере.
Ответ 4
@John Leidegren
У меня такое же требование, и я нашел ответ на недостающий бит вашего кода. Событие имеет 2 метода: "Добавить и удалить" и считается общедоступным, если любой из них является общедоступным. Так что это будет что-то вроде:
private bool IsVisible(EventInfo eventInfo)
{
return eventInfo.GetAddMethod(false) != null
|| eventInfo.GetRemoveMethod(false) != null;
}
хотя я не могу думать ни о какой причине, почему кто-то будет публичным, а не другим.
Ответ 5
Какой инструмент вы используете для создания документации? Я использую Sandcastle, и это дает вам возможность выбирать элементы для включения по доступности.
В целом кажется, что XML будет содержать всю информацию, которая нужна может, и ее инструмент обработки, чтобы выбрать из него то, что необходимо.
Ответ 6
У меня была та же проблема. SHFB медленнее, и, поскольку у нас есть другая база данных документации, нам не нужно было создавать документацию для нас.
В итоге я использовал XMLStarlet и отдельное пространство имен для внутренних классов. Например, все мои внутренние классы будут находиться в MyCompany.MyProduct.Internal
. Тогда я могу использовать одну простую команду
xml ed -L -d "//member[contains(@name, 'MyCompany.MyProduct.Internal')]" MyProduct.xml
чтобы очистить XML. Это, конечно, не пуленепробиваемый - он не охватывает внутренних членов в публичных классах, и он требует некоторой дисциплины, чтобы помнить о том, чтобы внутренние классы были внутренними. Но это самый чистый и наименее интрузивный метод, который работает для меня. Это также автономный EXE файл, легко проверяемый на сервере сборки, без пота.