Rick Strahl's Web Log

Wind, waves, code and everything in between...
ASP.NET • C# • HTML5 • JavaScript • AngularJs
Contact   •   Articles   •   Products   •   Support   •   Search
Ad-free experience sponsored by:
ASPOSE - the market leader of .NET and Java APIs for file formats – natively work with DOCX, XLSX, PPT, PDF, images and more

Generics in CodeDom vs. Generics with Reflection


:P
On this page:

In Html Help Builder's documentation imports from .NET code I have to manage two sets of interfaces: One that imports data using Reflection which is the more thourough mechanism that goes out, parses types and pulls in XML documentation. Getting Generics to work right in that code took a while to get right, but it's been working for some time.

The other end of things is the CodeDom side in the VS Editor. In Html Help Builder you can do two-way comment editing where you can sit on a method in code and pop up Html Help Builder to document it, then automatically inject the updated help text back into the document. I pretty much do all of my documentation this way as it's much easier to 'just' type documentation text into a text editor than deal with XML docs in the source code. It also addresses the issue of XML docs falling down as soon as you add angle brackets into your documentation (you have to escape any angle brackets)...

Anyway, on the editor side only some of the generics functionality has been working and I spent a bit more time digging deeper to see if I can make this experience better. After spending a few hours on this however, I'm none the wiser so I'm throwing this out here in case somebody has an idea how to match up the Generics signatures that are used by Reflection vs. the way that the CodeDom returns generic types, parameters etc.

Reflection identifies Generic types using some rather crytptic syntax which looks something like this:

HelpTest.HelpClass`2

for a class defined like this:

public class HelpClass<TKey, TValue>

In .NET 2.0 Reflection also gains a number of additional features to identify generic types (IsGeneric) and also provides something called a GenericParameterPosition which identifies a generic type used and its 'index'. In the example above TKey is index 0 and TValue is Index 1 and it's always represented in REflection by `0 and `1 instead of the actual generic type names.

So a method definition that uses these types looks like this:

HelpTest.HelpClass`2.SimpleGenericParm(`1,`0)

for a method like this:

public bool SimpleGenericParm(TValue Value, TKey Key)
 

It gets to be even more fun when you start looking at parameters that themselves use generic parameters. For example:

public TKey GetKey(List<TKey> KeyList, List<TValue> ValueList)
 
Turns into:
 
HelpTest.HelpClass`2.GetKey(System.Collections.Generic.List{`0},
System.Collections.Generic.List{`1})
 
While that's messy and all, there's a method to this madness and I have this working rather well for assembly 
based imports.
 
However, the CodeDom returns the information very differently - it basically returns the full type information as is in full descriptive mode. So the HelpClass above is returned as:
 
HelpTest.HelpClass<TKey,TValue>
 
Now that particular entry is reasonably easy to parse - it appears that Reflection basically creates that type as HelpTest.HelpClass`2, where the 2 stands for the two generic parmaters. This is easy to fix up with code like this:
 
 
/// <summary>
/// Fixes up a full generic classname to the syntax tha XML docs
/// use which uses a `1 to specify the number of generic parameters
/// </summary>
/// <param name="ClassName"></param>
/// <returns></returns>
public string FixUpGenericName(string ClassName)
{
    int At = ClassName.IndexOf("<");
 
    // *** IF it's a generic type we need to fix it up
    if (At > -1)
    {
        string TGenericParms = wwUtils.ExtractString(ClassName, "<", ">");
        string[] TParms = TGenericParms.Split(',');
 
        // *** Build the classname: MyClass<T,T1>  ->  MyClass`2
        ClassName = ClassName.Replace("<" + TGenericParms + ">","`" + TParms.Length.ToString() );
    }
 
    return ClassName;
}
 
That part is easy. But it took a while to get the whole thing straightened out to turn a CodeDom signature like this:
 
HelpTest.HelpClass<TKey, TValue>.SimpleGenericParm(TValue,TKey)
 
and map it to the Reflection based syntax which should look like this:
 
HelpTest.HelpClass`2.SimpleGenericParm(`1,`0)
 
As it turns out it's not that difficult to, which the following code demonstrates:
 
/// <summary>
/// This routine converts a full CodeDom sigature into a Reflection 
/// compatible Signature used by wwReflection's assembly import
/// 
/// HelpTest.HelpClass<TKey, TValue>.SimpleGenericParm(TValue,TKey)
/// HelpTest.HelpClass`2.SimpleGenericParm(`1,`0)
/// </summary>
/// <param name="Signature"></param>
/// <returns></returns>
public string GetReflectionSignatureFromCodeDom(string Signature)
{
    MatchCollection matches = Regex.Matches(Signature, "<.*?>");
 
    foreach (Match match in matches)
    {
        string[] GenericTypes = match.Value.Replace("<", "").Replace(">", "").Split(new char[1] { ',' },
                                                    StringSplitOptions.RemoveEmptyEntries);
 
        // *** Write the typename with the `2 prefix: Note if there are generic types (ie. List<TValue>) that
        // *** use Generic types these types will already be replaced and this match won't replace
        // *** which is actually the correct behavior - these types will later updated to { }'s instead of < >
        Signature = Signature.Replace(match.Value, "`" + GenericTypes.Length.ToString());
 
        // *** Now replace the generic parameters
        for (int x = 0; x < GenericTypes.Length; x++)
        {                    
            Signature = Signature.Replace(GenericTypes[x].Trim(), "`" + x.ToString());
        }
    }
 
    // *** If there are any generic parameters left they are for generic type refs in parms
    // *** Replace with {`1}
    Signature = Signature.Replace('<','{').Replace('>','}');
 
 
    return Signature;
}
 
Now this little bit of code actually solves a number of problems in my code both for popping up Html Help Builder in context, 
but also for editing and providing signatures for added members. It can now be called from another reusable routine
that generically returns a signature from a code element:
 
 
/// <summary>
        /// Returns a fully qualified signature for a given method.
        /// Example Signatures:
        /// NameSpace.Class.Property
        /// NameSpace.Class.Method(type Parm1, type Parm2)
        /// </summary>
        /// <param name="element"></param>
        /// <returns></returns>
        public string GetSignatureFromCodeElement(CodeElement element) 
        {
            string Sig = element.FullName;
 
         // *** TODO: This needs some work for Generic types. My<GenericParm> vs. My`1 in Type
 
            if (element.Kind == vsCMElement.vsCMElementFunction)  
            {
                CodeFunction func = element as CodeFunction;
                string Parms = "";
                foreach( CodeParameter parm in  func.Parameters )  
                {
                    string TypeName = parm.Type.AsFullName;
 
                    if (TypeName == null || TypeName == "")
                        TypeName = parm.Type.AsString;
 
                    Parms += TypeName + ",";
                }
                if (Parms.Length > 0)
                    Parms = Parms.Substring(0,Parms.Length-1);
 
                // *** Must fix up the constructor to match the constructor #ctor syntax
                if (  ( (CodeFunction) element).FunctionKind == vsCMFunction.vsCMFunctionConstructor) 
                    Sig = Sig.Substring(0,Sig.LastIndexOf(".")+1 ) + "#ctor";
 
                Sig = Sig + "(" + Parms + ")";
            }
 
            Sig = this.GetReflectionSignatureFromCodeDom(Sig);
 
 
            return Sig;
        }
 
Ah - the benefits of blogging! When I started writing this post I was fishing for a hopeful solution and here I am at the 
end of the post when the solution actually presented itself. Sweet!
 
Posted in .NET  Visual Studio  Addins  

The Voices of Reason


 

# DotNetSlackers: Generics in CodeDom vs. Generics with Reflection


Rick Strahl's Web Log
June 23, 2007

# Rick Strahl's Web Log


West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2020