How do I get a System.Type from a type name?

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 elaborate 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 (without locking)
http://blogs.clariusconsulting.net/kzu/retrieving-available-types-in-current-project-and-its-references-withoult-locking/

and

How to get a System.Type from an EnvDTE.CodeTypeRef or EnvDTE.CodeClass
http://blogs.clariusconsulting.net/kzu/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.

TechDays 2008

This week I attended the TechDays 2008 here at Madrid, a two-days event to launch Visual Studio 2008, Windows Server 2008 and SQL Server 2008 (this product actually is not released yet but…). I don’t know if it was that Microsoft Iberica (the subsidiary for Spain and Portugal) celebrated its 20 anniversary or that the new products deserved it, but this has been the greatest Microsoft event that I ever attended (except the Microsoft TechEd at Barcelona that I attend every some years). It was like a mini-TechEd, with lots of parallel sessions, hands-on labs, ask the expert, partners expo, etc. and even hot dogs ;-). Lots of known people (many speakers were fellow MVPs) and very good content in most sessions. Apart from the new exciting technologies such as WCF, WFF, WPF or SilverLight I was mostly interested in Team Foundation Server 2008, a great tool for collaboration and team work that hopefully I will install and use this year, and which furthermore provides extensibility, so who knows if I enter that area some day…

It’s now official: the Visual Studio Gallery

Microsoft announced it some days ago: the new Visual Studio Gallery (http://www.visualstudiogallery.com), a new web site with lots of 3rd party products for Visual Studio, such as controls, source control providers, add-ins, packages, etc. Considering how hard can be sometimes the Visual Studio extensibility, it’s great to see the big ecosystem of tools that many people is building around Microsoft Visual Studio. The Eclipse IDE for Java has had always such a big ecosystem of plugins (I used Eclipse with some plugins to develop a J2ME game for Java-enabled mobile phones a couple of years ago) and now Visual Studio has such ecosystem in a centralized website, easy to find and navigate.

Of course, my MZ-Tools add-in is part of such ecosystem:

http://www.visualstudiogallery.com/ExtensionDetails.aspx?ExtensionId=3b070c5d-6478-4a86-aafa-026e79189e9e

MZ-Tools Articles Series: BUG: SolutionBuild.Build method builds only one project in Visual Studio add-ins or macros

I thought I already posted a blog entry about my last article:

BUG: SolutionBuild.Build method builds only one project in Visual Studio add-ins or macros
http://www.mztools.com/articles/2008/MZ2008002.aspx

but it seems that no, so there it is. Today I was adding another post with more info:

Someone posted a question about this in the MSDN forums and I suggested to try the interfaces of the SDK (such as IVsSolutionBuildManager) instead of the automation model (EnvDTE). I have had a little time to actually try it and, alas, it doesn’t work too, so it seems that the bug happens in the service and not in the automation model. If you load the add-in for a solution with two projects, you will notice that only the startup project is actually built, the same problem that using the automation model. If this bug is important for you, vote for it on the Microsoft Connect web site:

http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=320688

As you will notice, the bug was opened three times, somehow MS failed to reproduce it twice and close it, and when finally they reproduced it their explanation is that if you have the Tools, Options, Projects and Solutions, Build and Run, “Only build startup projects and dependencies on Run” option set to True, then SolutionBuild should honor it. This is absurd, because nobody is talking about “Run” here, we are talking about “Build”, which is a different thing. If I ask programmatically SolutionBuild.Build, I would expect the whole solution to be built…

The workaround is to iterate all the projectsof the solution (taking into account the dependencies, I guess) and build each one individually, this is insane…

Below is the code using the SDK just in case you are interested.

Imports System
Imports Microsoft.VisualStudio.CommandBars
Imports Extensibility
Imports EnvDTE
Imports EnvDTE80
Imports System.Runtime.InteropServices
Imports Microsoft.VisualStudio.Shell.Interop

Public Class Connect
   Implements IDTExtensibility2

   <ComVisible(True), ComImport(), Guid("6D5140C1-7436-11CE-8034-00AA006009FA"), _
   InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)> _
   Friend Interface IServiceProvider
      <PreserveSig()> _
      Function QueryService(<InAttribute()> ByRef guidService As Guid, _
      <InAttribute()> ByRef riid As Guid, <OutAttribute()> ByRef ppvObject As IntPtr) As Integer
   End Interface

   Private m_objDTE2 As DTE2

   Public Sub OnConnection(ByVal application As Object, ByVal connectMode As ext_ConnectMode, ByVal addInInst As Object, ByRef custom As Array) Implements IDTExtensibility2.OnConnection

      m_objDTE2 = CType(application, DTE2)

      Select Case connectMode

         Case ext_ConnectMode.ext_cm_Startup

            ' OnStartupComplete will be called automatically

         Case ext_ConnectMode.ext_cm_AfterStartup

            OnStartupComplete(Nothing)

      End Select

   End Sub

   Public Sub OnStartupComplete(ByRef custom As Array) Implements IDTExtensibility2.OnStartupComplete

      BuildSolution()

   End Sub

   Private Sub BuildSolution()

      Dim objIVsSolutionBuildManager As IVsSolutionBuildManager
      Dim iResult As Integer

      objIVsSolutionBuildManager = DirectCast(GetService(m_objDTE2, GetType(IVsSolutionBuildManager)), IVsSolutionBuildManager)

      iResult = objIVsSolutionBuildManager.StartSimpleUpdateSolutionConfiguration(CType(VSSOLNBUILDUPDATEFLAGS.SBF_OPERATION_BUILD Or VSSOLNBUILDUPDATEFLAGS.SBF_OPERATION_FORCE_UPDATE, UInt32), 0, 0)

   End Sub

   Friend Function GetService(ByVal serviceProvider As Object, ByVal type As System.Type) As Object

      Return GetService(serviceProvider, type.GUID)

   End Function

   Friend Function GetService(ByVal serviceProvider As Object, ByVal guid As System.Guid) As Object

      Dim objService As Object = Nothing
      Dim objIServiceProvider As IServiceProvider
      Dim objIntPtr As IntPtr
      Dim hr As Integer
      Dim objSIDGuid As Guid
      Dim objIIDGuid As Guid

      objSIDGuid = guid
      objIIDGuid = objSIDGuid
      objIServiceProvider = CType(serviceProvider, IServiceProvider)
      hr = objIServiceProvider.QueryService(objSIDGuid, objIIDGuid, objIntPtr)

      If hr <> 0 Then

         System.Runtime.InteropServices.Marshal.ThrowExceptionForHR(hr)

      ElseIf Not objIntPtr.Equals(IntPtr.Zero) Then

         objService = System.Runtime.InteropServices.Marshal.GetObjectForIUnknown(objIntPtr)
         System.Runtime.InteropServices.Marshal.Release(objIntPtr)

      End If

      Return objService

   End Function

   Public Sub OnDisconnection(ByVal disconnectMode As ext_DisconnectMode, ByRef custom As Array) Implements IDTExtensibility2.OnDisconnection
   End Sub

   Public Sub OnAddInsUpdate(ByRef custom As Array) Implements IDTExtensibility2.OnAddInsUpdate
   End Sub

   Public Sub OnBeginShutdown(ByRef custom As Array) Implements IDTExtensibility2.OnBeginShutdown
   End Sub

End Class

MZ-Tools Articles Series: HOWTO: Detect installed Visual Studio editions, packages or service packs

My latest article is about guessing the installed Visual Studio versions (2005, 2008), editions (Std, Prof, Team Edition, Team Suite), packages or service packs, either from a setup program or from a running add-in or package:

HOWTO: Detect installed Visual Studio editions, packages or service packs
http://www.mztools.com/articles/2008/MZ2008003.aspx

More on “HOWTO: Using the Choose Data Source dialog of Visual Studio 2005 from your own code”

Almost one year ago I wrote an article on my web site about how to show the Choose Data Source dialog of Visual Studio from you own code (an add-in, for example). It happens that the approach that I showed works only with the .NET data providers supplied with the .NET Framework (namely SQL Server, Oracle, ODBC and OLEDB), but not with 3rd party providers such as ODP.NET for Oracle, etc. In fact I knew that when I wrote the article but it was not until yesterday when I had to revisit it to use it. I spent several hours struggling with the Visual Studio implementation (which shows 3rd party .NET data providers) and finally I figured it out, so I have updated the article:

HOWTO: Using the Choose Data Source dialog of Visual Studio 2005 from your own code
http://www.mztools.com/articles/2007/MZ2007011.aspx

As you can see, it’s actually quite simple when you know how to do it, but if you have to investigate, the Visual Studio internal implementation is so complex as a DataProvider class that inherits from another class named DataProvider (yep, same name but from another assembly), which implements IVSDataProvider and if that is not enough you have another VSDataProvider class. And this mess repeats with another classes such as DataConnectionDialog, etc. I wonder if this is the kind of things that make VS so slow…  All this is caused because VS extends an independent implementation of the Choose Data Source dialog to inject VS-only registered DDEX providers (a DDEX is a design-time extension for an ADO.NET 2.0 provider). It would be much better to register the DDEX providers outside the Visual Studio registry key and make the Microsoft.Data.*.dll assemblies recognize 3rd party DDEX providers for the Choose Data Source dialog.

BTW, when you want to reference a Visual Studio assembly in the installation folder, MVP fellow Daniel Cazzulino has a great post about doing it without hardcoding its path. It would be great if VS did it automatically, but when he reported back to Microsoft, the engineer could not reproduce it. Too bad, because it is quite easy to reproduce the problem…you can vote for it in the link provided in that post.

Anyway, I think that the updated article solves completely the Choose Data Source dialog problem. Although DDEX came late to Visual Studio (it was not until 2005) we are seeing more and more 3rd party DDEX providers truly integrating inside Visual Studio, that is, using the Visual Studio Server Explorer instead its own explorer toolwindow (such as the Oracle Explorer of ODP.NET 10.x), and using the standard Choose Data Source dialog, etc. This is great news for Visual Studio!

New year, MVP again and first article of 2008

Hi all,

I have started the new year with a MVP title for another year (thanks Microsoft!) and I have started to write more articles about Visual Studio extensibility. The one of today is about the folders that Visual Studio uses to locate .Addin files (XML registration). It happens that Visual Studio 2005 uses 5 folders by default and Visual Studio 2008 uses 6, and both allows you to add even more folders. I wonder why we should need more than one folder (to register an add-in for all users, which is the correct thing) and maybe another to register the add-in only for the current user (for development purposes only). But Microsoft thinks otherwise, and not surprisingly, the result is a mess. If you follow the MSDN Visual Studio 2008 documentation about this topic, you will find that:

  • 1 location uses the hardcoded value “Application Data” that every non-English developer knows it is a sin.
  • 3 locations are incorrectly documented. If you place the .AddIn file in the documented folder, the Add-in Manager won’t show it.
  • 1 new location for Visual Studio 2008 is not documented. An even if it was, it happens that there is a bug in VS 2008 and it doesn’t work.

So, 6-1-3-1 = 1 folder, which is all we should need to register add-ins.

Furthermore, VS uses proprietary placeholders such as %VSCOMMONDATA% but there seems to be no API or registry entry to get that programatically, so your setup will have to use some workaround as explained in the article.

I wish developers don’t complicate things unnecessarily… the software would have less bugs…

Here is the article:

INFO: Default .AddIn file locations for Visual Studio add-ins
http://www.mztools.com/Articles/2008/MZ2008001.aspx

VS SDK, packages, add-ins, macros and more…