Page 40 - MSDN Magazine, October 2019
P. 40
to deal with nested types. The replacement of the bracketed string by “Regex” handles assembly information on the FullName of the Type. The MethodInfo and ConstructorInfo extension methods are a little trickier. Constructors and methods can both have parameters— arrays, pointers, ref/in/out types, generic types and so forth. Methods
Figure 5 Parameters to Strings Pseudo Code
can even define their own generic type parameters. It’s easiest to start by storing all the generic parameters in dictionaries, like so:
Dictionary<string, int> typeGenericMap = new Dictionary<string, int>(); int tempTypeGeneric = 0; Array.ForEach(methodInfo.DeclaringType.GetGenericArguments(),
x => typeGenericMap\[x.Name\] = tempTypeGeneric++);
Dictionary<string, int> methodGenericMap = new Dictionary<string, int>(); int tempMethodGeneric = 0; Array.ForEach(methodInfo.GetGenericArguments(),
x => methodGenericMap.Add(x.Name, tempMethodGeneric++));
ParameterInfo\[\] parameterInfos = methodInfo.GetParameters();
With the generic parameters stored in dictionaries, you can easily obtain their indices. Remember that the generic parameters appear in the XML documentation as apostrophes followed by the index. However, generic parameters aren’t the only special type you need to handle. Arrays, reference parameters, and pointers also have unique syntax in the XML file. Figure 5 has some pseudo code for converting the ParameterInfos into strings.
Figure 5 is heavily simplified, but hopefully it gets the idea across. Check out my project on GitHub to see the full code for the Construc- torInfo and MethodInfo extension methods (github.com/ZacharyPatten/Towel).
MemberInfo is a base class for the reflection types. You can make an extension method for the MemberInfo type that funnels into the extension methods for the specific types, as shown in Figure 6.
Don’t forget ParameterInfo. It has a Member property that returns the MemberInfo for the parameter. Just call the member- Info GetDocumentation extension method shown in Figure 6 and extract the XML for the specific parameter if it exists. Figure 7 shows how this is done.
Automatically Loading the XML Files as Needed
Is there a way to bypass the loading function? If you follow the stan- dard of having your output XML files in the same directory and with the same name as your assemblies, it’s easy to automatically look up an XML file so you don’t need to call the loading function.
You can get the assembly from any type with the GetAssembly method or the Assembly property. Then you can get the file loca- tion of an assembly through the CodeBase property of the assembly. Finally, just alter the file path to look for the XML file instead of the assembly, and call the loading function shown in Figure 4. You can see how this works in Figure 8.
Then you can update the extension methods to load the XML documentation of the assembly if it hasn’t already been loaded. The following code shows the System.Type extension method from before with automatic XML file loading:
public static string GetDocumentation(this Type type) \{
LoadXmlDocumentation(type.Assembly);
// ... Rest of the code \}
Usage Examples
Now that you have all of the necessary framework code, you just need to call it. Don’t forget to add a using statement at the top of the file so the extension methods are visible. Figure 9 shows usage examples for printing the XML documentation of the cur- rent assembly to the console.
foreach (var parameterInfo in parameterInfos) \{
if (parameterInfo.ParameterType.HasElementType) \{
// The type is either an array, pointer, or reference if (parameterInfo.ParameterType.IsArray) \{
// Append the "\[\]" array brackets onto the element type \}
else if (parameterInfo.ParameterType.IsPointer) \{
// Append the "*" pointer symbol to the element type
\}
else if (parameterInfo.ParameterType.IsByRef) \{
// Append the "@" symbol to the element type \}
\}
else if (parameterInfo.ParameterType.IsGenericParameter) \{
// Look up the index of the generic from the // dictionaries in Figure 5, appending "`" if // the parameter is from a type or "``" if the // parameter is from a method
\} else \{
// Nothing fancy, just convert the type to a string \}
\}
Figure 6 MemberInfo Extension Method
public static string GetDocumentation(this MemberInfo memberInfo) \{
if (memberInfo.MemberType.HasFlag(MemberTypes.Field)) \{ return ((FieldInfo)memberInfo).GetDocumentation();
\}
else if (memberInfo.MemberType.HasFlag(MemberTypes.Property)) \{
return ((PropertyInfo)memberInfo).GetDocumentation(); \}
else if (memberInfo.MemberType.HasFlag(MemberTypes.Event)) \{ return ((EventInfo)memberInfo).GetDocumentation();
\}
else if (memberInfo.MemberType.HasFlag(MemberTypes.Constructor)) \{
return ((ConstructorInfo)memberInfo).GetDocumentation(); \}
else if (memberInfo.MemberType.HasFlag(MemberTypes.Method)) \{ return ((MethodInfo)memberInfo).GetDocumentation();
\}
else if (memberInfo.MemberType.HasFlag(MemberTypes.TypeInfo) ||
memberInfo.MemberType.HasFlag(MemberTypes.NestedType)) \{
return ((TypeInfo)memberInfo).GetDocumentation(); \}
else \{
return null;
\} \}
Figure 7 ParameterInfo Extension Method
public static string GetDocumentation(this ParameterInfo parameterInfo) \{
string memberDocumentation = parameterInfo.Member.GetDocumentation(); if (memberDocumentation != null) \{
string regexPattern =
Regex.Escape(@"<param name=" + "\\"" + parameterInfo.Name + "\\"" + @">") + ".*?" +
Regex.Escape(@"</param>");
Match match = Regex.Match(memberDocumentation, regexPattern); if (match.Success) \{
return match.Value; \}
\}
return null; \}
36 msdn magazine
C#