// Reading XML Documentation at Run-Time
// Bradley Smith - 2010/11/25
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Xml.Linq;
using System.Xml.XPath;
/// <summary>
/// Provides extension methods for reading XML comments from reflected members.
/// </summary>
public static class XmlDocumentationExtensions {
private static Dictionary<string, XDocument> cachedXml;
/// <summary>
/// Static constructor.
/// </summary>
static XmlDocumentationExtensions() {
cachedXml = new Dictionary<string, XDocument>(StringComparer.OrdinalIgnoreCase);
}
/// <summary>
/// Returns the expected name for a member element in the XML documentation file.
/// </summary>
/// <param name="member">The reflected member.</param>
/// <returns>The name of the member element.</returns>
private static string GetMemberElementName(MemberInfo member) {
char prefixCode;
string memberName = (member is Type)
? ((Type)member).FullName // member is a Type
: (member.DeclaringType.FullName + "." + member.Name); // member belongs to a Type
switch (member.MemberType) {
case MemberTypes.Constructor:
// XML documentation uses slightly different constructor names
memberName = memberName.Replace(".ctor", "#ctor");
goto case MemberTypes.Method;
case MemberTypes.Method:
prefixCode = 'M';
// parameters are listed according to their type, not their name
string paramTypesList = String.Join(
",",
((MethodBase)member).GetParameters()
.Cast<ParameterInfo>()
.Select(x => x.ParameterType.FullName
).ToArray()
);
if (!String.IsNullOrEmpty(paramTypesList)) memberName += "(" + paramTypesList + ")";
break;
case MemberTypes.Event:
prefixCode = 'E';
break;
case MemberTypes.Field:
prefixCode = 'F';
break;
case MemberTypes.NestedType:
// XML documentation uses slightly different nested type names
memberName = memberName.Replace('+', '.');
goto case MemberTypes.TypeInfo;
case MemberTypes.TypeInfo:
prefixCode = 'T';
break;
case MemberTypes.Property:
prefixCode = 'P';
break;
default:
throw new ArgumentException("Unknown member type", "member");
}
// elements are of the form "M:Namespace.Class.Method"
return String.Format("{0}:{1}", prefixCode, memberName);
}
/// <summary>
/// Returns the XML documentation (summary tag) for the specified member.
/// </summary>
/// <param name="member">The reflected member.</param>
/// <returns>The contents of the summary tag for the member.</returns>
public static string GetXmlDocumentation(this MemberInfo member) {
AssemblyName assemblyName = member.Module.Assembly.GetName();
return GetXmlDocumentation(member, assemblyName.Name + ".xml");
}
/// <summary>
/// Returns the XML documentation (summary tag) for the specified member.
/// </summary>
/// <param name="member">The reflected member.</param>
/// <param name="pathToXmlFile">Path to the XML documentation file.</param>
/// <returns>The contents of the summary tag for the member.</returns>
public static string GetXmlDocumentation(this MemberInfo member, string pathToXmlFile) {
AssemblyName assemblyName = member.Module.Assembly.GetName();
XDocument xml = null;
if (cachedXml.ContainsKey(assemblyName.FullName))
xml = cachedXml[assemblyName.FullName];
else
cachedXml[assemblyName.FullName] = (xml = XDocument.Load(pathToXmlFile));
return GetXmlDocumentation(member, xml);
}
/// <summary>
/// Returns the XML documentation (summary tag) for the specified member.
/// </summary>
/// <param name="member">The reflected member.</param>
/// <param name="xml">XML documentation.</param>
/// <returns>The contents of the summary tag for the member.</returns>
public static string GetXmlDocumentation(this MemberInfo member, XDocument xml) {
return xml.XPathEvaluate(
String.Format(
"string(/doc/members/member[@name='{0}']/summary)",
GetMemberElementName(member)
)
).ToString().Trim();
}
/// <summary>
/// Returns the XML documentation (returns/param tag) for the specified parameter.
/// </summary>
/// <param name="parameter">The reflected parameter (or return value).</param>
/// <returns>The contents of the returns/param tag for the parameter.</returns>
public static string GetXmlDocumentation(this ParameterInfo parameter) {
AssemblyName assemblyName = parameter.Member.Module.Assembly.GetName();
return GetXmlDocumentation(parameter, assemblyName.Name + ".xml");
}
/// <summary>
/// Returns the XML documentation (returns/param tag) for the specified parameter.
/// </summary>
/// <param name="parameter">The reflected parameter (or return value).</param>
/// <param name="pathToXmlFile">Path to the XML documentation file.</param>
/// <returns>The contents of the returns/param tag for the parameter.</returns>
public static string GetXmlDocumentation(this ParameterInfo parameter, string pathToXmlFile) {
AssemblyName assemblyName = parameter.Member.Module.Assembly.GetName();
XDocument xml = null;
if (cachedXml.ContainsKey(assemblyName.FullName))
xml = cachedXml[assemblyName.FullName];
else
cachedXml[assemblyName.FullName] = (xml = XDocument.Load(pathToXmlFile));
return GetXmlDocumentation(parameter, xml);
}
/// <summary>
/// Returns the XML documentation (returns/param tag) for the specified parameter.
/// </summary>
/// <param name="parameter">The reflected parameter (or return value).</param>
/// <param name="xml">XML documentation.</param>
/// <returns>The contents of the returns/param tag for the parameter.</returns>
public static string GetXmlDocumentation(this ParameterInfo parameter, XDocument xml) {
if (parameter.IsRetval || String.IsNullOrEmpty(parameter.Name))
return xml.XPathEvaluate(
String.Format(
"string(/doc/members/member[@name='{0}']/returns)",
GetMemberElementName(parameter.Member)
)
).ToString().Trim();
else
return xml.XPathEvaluate(
String.Format(
"string(/doc/members/member[@name='{0}']/param[@name='{1}'])",
GetMemberElementName(parameter.Member),
parameter.Name
)
).ToString().Trim();
}
}