Page 39 - MSDN Magazine, October 2019
P. 39
Figure 2 The XML Output
Enough background—let’s get into the code for accessing XML documentation. You first need to load the XML file into memory, which can be easily done using the XmlReader class. Content can be stored using Dictionary<string, string>. The key for the dictio- nary will be the name property as it exists in the XML file, and the value will be the content of the XML documentation (the inner XML of the member tag in the XML file). See Figure 3.
Next, let’s explore accessing XML from the dictionary. The reflec- tion types in the System.Reflection namespace represent types and members of compiled code: Type, FieldInfo, MethodInfo, Construc- torInfo, PropertyInfo, EventInfo, MemberInfo and ParameterInfo. You can create extension methods that let you call the methods as if they were instance methods on the reflection types. In the extension methods, you just need to convert the reflection type into the key of the dictionary that holds the loaded XML documentation. I created an extension method called GetDocumentation, as shown in Figure 4.
The extension methods for EventInfo and FieldInfo should be iden- tical to the PropertyInfo method, just with “E:” and “F:” prefix strings, respectively. Note that the replacement of the “+” symbol with the “.” is
<?xml version="1.0"?> <doc>
<assembly> <name>Example</name>
</assembly> <members>
<member name="T:Example.ExampleClass">
<summary>XML Documentation on ExampleClass.</summary>
</member>
<member name="M:Example.ExampleClass.ExampleMethod1">
<summary>XML Documentation on ExampleMethod1.</summary> </member>
<member name="T:Example.ExampleClass.ExampleNestedGenericClass`3">
<summary>XML Documentation on ExampleNestedGenericClass.</summary> <typeparam name="A">Generic type A.</typeparam>
<typeparam name="B">Generic type B.</typeparam>
<typeparam name="C">Generic type C.</typeparam>
</member>
<member name="M:Example.ExampleClass.ExampleNestedGenericClass`3. ExampleMethod2``3(`0,``0,`1\[\],``1\[\],`2\[0:,0:,0:\],``2\[0:,0:,0:\])">
<summary>XML Documentation on ExampleMethod2.</summary> <typeparam name="D">Generic type D.</typeparam> <typeparam name="E">Generic type E.</typeparam> <typeparam name="F">Generic type F.</typeparam>
<param name="a">Parameter a.</param> <param name="d">Parameter d.</param> <param name="b">Parameter b.</param> <param name="e">Parameter e.</param> <param name="c">Parameter c.</param> <param name="f">Parameter f.</param>
</member> </members>
</doc>
Figure 3 XML Loading Function
internal static Dictionary<string, string> loadedXmlDocumentation = new Dictionary<string, string>();
public static void LoadXmlDocumentation(string xmlDocumentation) \{
using (XmlReader xmlReader = XmlReader.Create(new StringReader(xmlDocumentation))) \{
while (xmlReader.Read()) \{
if (xmlReader.NodeType == XmlNodeType.Element && xmlReader.Name == "member") \{
string raw_name = xmlReader\["name"\];
loadedXmlDocumentation\[raw_name\] = xmlReader.ReadInnerXml(); \}
\} \}
\}
Microsoft’s language guides detail all the recommended tags, such as summary, param, typeparam, returns and remarks. However, you can create tags of your own for XML documentation. For example, I like to include my own tags like “citation” and “runtime.” The compiler should extract any properly formatted XML tag from the comments.
The XML file produced at compile time is in a very simple format, with an assembly tag at the top to denote what assembly the file is documenting, and then every XML block from source code is placed into a separate member tag. The name property on the member XML tags represents the type/member of the code that the documentation is for. Figure 1 includes C# source code and Figure 2 shows the XML output from that source code.
The prefix of the name property determines what kind of code element the documentation is for, as follows: Methods “M:”; Types “T:”; Fields “F:”; Properties “P:”; Constructors “M:”; Events “E:”.
The remaining parts of the name property are the fully qualified type and member names, along with any necessary parameters and/or generic parameters. Generic parameters from types are represented with a single apostrophe followed by the index “`X,” while generic parameters from methods are represented with two apostrophes followed by the index “``Y.” Arrays and unsafe pointers have the same syntax as they have in the source code; however, multidimensional arrays with a rank greater than one include “0:” strings separated by commas for each rank. Ref/Out/In parameters are all handled the same with just an at sign (@) appended to the end of the type. Optional parameters (with default values) don’t have any special formatting.
As you can see, XML name properties can get a little complicated as you factor in various features of the languages, but the important takeaway is that the reflection types in .NET include all the logic nec- essary to build the “name” properties as they appear in the XML file. msdnmagazine.com
Figure 4 Format the Key Strings
// Helper method to format the key strings private static string XmlDocumentationKeyHelper(
string typeFullNameString,
string memberNameString) \{
string key = Regex.Replace( typeFullNameString, @"\\\[.*\\\]", string.Empty).Replace('+', '.');
if (memberNameString != null) \{
key += "." + memberNameString; \}
return key; \}
public static string GetDocumentation(this Type type) \{
string key = "T:" + XmlDocumentationKeyHelper(type.FullName, null); loadedXmlDocumentation.TryGetValue(key, out string documentation); return documentation;
\}
public static string GetDocumentation(this PropertyInfo propertyInfo) \{
string key = "P:" + XmlDocumentationKeyHelper( propertyInfo.DeclaringType.FullName, propertyInfo.Name);
loadedXmlDocumentation.TryGetValue(key, out string documentation);
return documentation; \}
October 2019 35