This is another question that appears from time to time in the forums, and it is one extremely complicated to have a 100% satisfaction with the result :-). I haven’t written a MZ-Tools Series article for this since I don’t have a comprehensive answer with a sample code, but I will ellaborate a bit about this in this post.
The question is, suppose I have a string like “Car” that I know it’s a type. How can I get the System.Type that represents that type? Before that question there is a previous one: how did I get that string? There are several ways:
- Using the code model (EnvDTE.Project.CodeModel or EnvDTE.ProjectItem.FileCodeModel), you have a CodeVariable, CodeParameter, etc. which has a CodeTypeRef property that returns the type of the code element.
- Parsing a procedure code (by hand, since the code model doesn’t cover that), you get statements like “Dim objCar as Car” (VB.NET) or “Car mycar” (C#), so somehow you get the “Car” string and you guess that it is a type.
There are a couple of complications to note here:
- Types have a short name, such as “Car”, and a fully qualified name, such as “Vehicles.Motorized.Car”. Since you can have “Imports” statements at file or project level in VB.NET and “using” statements at file level in C#, it is not required that the fully qualified name to appear in the variable or parameter declaration. Note: even if the declaration contains a dot (.) you can’t assure that it is a fully qualified name because it can be a partially-qualified name, such as if you import the “Vehicles” namespace at file level and you declare the variable as Dim objCar As Motorized.Car.
- Types can reside in compiled assemblies references, or, alas, in the project’s source code or in a project (not compiled) reference. In the last two cases, you can’t have a System.Type, since maybe the project is not compiled yet (first time) or maybe the last compilation was days ago and it is not up-to-date. In these cases, for most accuracy you don’t really want a System.Type but an EnvDTE.CodeElement such as an EnvDTE.CodeClass, EnvDTE.CodeStruct, EnvDTE.CodeInterface, etc. So, your add-in has to deal with two kind of types: compiled types (represented by a System.Type) and source code types (represented by a CodeElement).
So, let’s try to solve them:
- To get the fully qualified name of a type name, assumming that it comes from a EnvDTE.CodeTypeRef, you can try the CodeTypeRef.AsFullName property (in contrast to its CodeTypeRef.AsString property). I don’t know how reliable is this in each version of Visual Studio and for each language (VB.NET, C#) but my experience is that you can’t fully trust the code model. As a workaround, you should compose a list of fully qualified candidate type names combining the imported namespaces and then try to resolve which of them is the true one. This is what the compiler does after all when building the project, although it doesn’t expose that information to add-ins or packages (AFAIK).
- Given the fully qualified name, or a list of candidates, you need to “resolve” them to a System.Type or EnvDTE.CodeElement. To do this, I would start with compiled references since is faster than using the code model. You would have to get the references of the project which are compiled assemblies (see HOWTO: Getting information specific to VB.NET and C# projects from an add-in or macro). For each compiled reference you get the name of the assembly and using Reflection you can use Assembly.Load or Assembly.LoadFrom to load it and then call Assembly.GetExportedTypes to get the public types and then iterate them until you find the one that matches the fully qualified type name. If you don’t find it, then chances are that it is a type declared in the source code of the project, or in one of the project (not compiled) references. In these cases you have to navigate the code model of each project (see HOWTO: Navigate the files of a solution from a Visual Studio .NET macro or add-in and HOWTO: Navigate the code elements of a file from a Visual Studio .NET macro or add-in). Needless to say, this can be slow.
All that said, you may have heard of the ITypeResolutionService service of Visual Studio, which sounds promising to avoid all that. I haven’t tried it personally because it was not clear for me how to get an instance of it (most documentation refers to component designers) but today I found two posts of MVP fellow Daniel Cazzulino that can be helpful:
Retrieving available types in current project and its references (withoult locking)
How to get a System.Type from an EnvDTE.CodeTypeRef or EnvDTE.CodeClass
So, he explains how to get the all the types declared in a project and references using the ITypeDiscoveryService, and how to get a System.Type from a type name using the ITypeResolutionService, retrieving both services from an EnvDTE.Project. These is great news, but notice the caveat that for types declared in source code, it will retrieve a System.Type from the last successful compilation (if any) and not an EnvDTE.CodeElement. Assuming that you don’t mind the issue of the out-of-date compilation, getting a System.Type can be enough for some scenarios, but not for others, such as when you want to go to the source code file of the type (think the Go To Definition command of Visual Studio), where you really want an EnvDTE.CodeElement (which contains the ProjectItem and location, etc.) and not a System.Type.