Hack 56. Automatically Add using and Imports Statements
Use a macro to scan your code and determine which namespaces need to be imported. The .NET Framework is built around namespaces. Every class in the .NET Framework belongs to a namespace; you need to tell the compiler that you want to use the classes in that namespace. This is done through the use of a using statement in C# or an Imports statement in VB.NET. One of the annoyances that this creates is then realizing that you have not added a using or Imports statement for a class that you added. This means you have to find out what namespace you need to add for this class and then go to the top of your document and add the name of the namespace. Alternatively, you could fully qualify the class you are using, meaning that you prefix the name of the class with the full name of the namespace (e.g., System.Data.SqlClient.SqlConnection).A set of macros that will do either of these for you automatically is available. The first macro is called AddDirective and will look for your class and then add the Imports or using statement for you. To use this macro, you simply need to highlight the name of your class and then run the AddDirective macro. If you highlight SqlConnection and run the macro, it will add using System.Data.SqlClient; or Imports System.Data.SqlClient to the top of your document.The other macro is called AddNamespace and will, instead of adding a using or Imports statement, fully qualify the name of your class. If you highlight SqlConnection and run the macro, it will add System.Data.SqlClient to the front of your class.The macro works by cycling through all the assemblies referenced by your project. If it finds a class in multiple namespaces, the first namespace found is used.This macro was written by a developer named Jan Tielens, and its code is shown here (this one is pretty long; you may want to download it from the book site):
'TypeFinder Macro, version 2.11 'For Visual Studio.NET 'By Jan Tielens, http://weblogs.asp.NET/jan 'With the help of: '- Yves Hanoulle, http://www.hanoulle.be/ '- Thomas Freudenberg '- Guillaume Roberge Imports EnvDTE Imports System Imports System.Collections Imports System.Diagnostics Imports System.Reflection Imports System.IO Public Module TypeFinder Private Class TypeCache Public Shared Cache As Hashtable = New Hashtable End Class Private Function SearchTypeInAssembly( _ ByVal typename As String, _ ByVal assm As Reflection.Assembly) As Type DTE.StatusBar.Text = "Searching for '" & _ typename & "' " & assm.GetName.Name & "..." If (Not TypeCache.Cache.ContainsKey(typename)) Then Dim t As Type For Each t In assm.GetTypes If t.Name.ToLower = typename Then TypeCache.Cache.Add(typename, t) Return t End If Next Else Return TypeCache.Cache.Item(typename) End If End Function Private Function SearchType(ByVal typename As String) As Type typename = _ typename.ToLower.Trim.Replace(";", ").Replace(".", ") Dim assm As [Assembly] Dim currentAssm As String Dim currentPath As String Dim t As Type Dim p As [Property] 'search in principal assembly assm = Reflection.Assembly.LoadWithPartialName("mscorlib") t = SearchTypeInAssembly(typename, assm) If Not t Is Nothing Then Return t 'search in assemblies in solutions Dim proj As Project For Each proj In DTE.ActiveSolutionProjects 'search in references of current project Dim ref As VSLangProj.Reference For Each ref In proj.Object.References assm = Reflection.Assembly.LoadFrom(ref.Path) t = SearchTypeInAssembly(typename, assm) If Not t Is Nothing Then Return t Next currentAssm = " currentPath = " 'obtain properties to get assembly 'associated with project For Each p In proj.Properties If (p.Name = "OutputFileName") Then currentAssm = p.Value ElseIf (p.Name = "LocalPath") Then currentPath = p.Value End If If currentAssm.Length > 0 _ And currentPath.Length > 0 Then Exit For Next 'search in the assembly associated with the project currentAssm = currentPath + "bin\" + _ DTE.Solution.SolutionBuild.ActiveConfiguration.Name _ + "\" + currentAssm Dim tempAss As String = currentAssm + "2" Try Try If (File.Exists(tempAss)) Then File.Delete(tempAss) End If Catch End Try 'we copy the assembly to be sure that when the 'compilation will be launch the file 'could be written File.Copy(currentAssm, tempAss) assm = Reflection.Assembly.LoadFrom(tempAss) t = SearchTypeInAssembly(typename, assm) Try File.Delete(tempAss) Catch End Try If Not t Is Nothing Then Return t Catch ex As Exception End Try Next DTE.StatusBar.Text = "Could not find type '" _ & typename & "'" DTE.StatusBar.Highlight(True) Return Nothing End Function Public Sub AddNamespace( ) Dim text As TextSelection = DTE.ActiveDocument.Selection If (text.Text.Length = 0) Then text.WordLeft(True) Dim t As Type = SearchType(text.Text) If Not t Is Nothing Then text.Text = t.FullName text.EndOfLine( ) DTE.StatusBar.Text = "Ready" End If End Sub Public Sub AddDirective( ) Dim text As TextSelection = DTE.ActiveDocument.Selection If (text.Text.Length = 0) Then text.WordLeft(True) Dim t As Type = SearchType(text.Text) If Not t Is Nothing Then Dim keyword, suffix As String Dim line As Integer = text.AnchorPoint.Line text.Text = t.Name Select Case DTE.ActiveDocument.Language Case "CSharp" keyword = "using" suffix = ";" Case "Basic" keyword = "Imports" suffix = String.Empty Case Else Throw New System.Exception("Invalid Language: " _ & DTE.ActiveDocument.Language) End Select Dim alreadyUsed As Boolean = False text.StartOfDocument( ) 'Skip the headers text.WordRight(True, 2) While text.Text.StartsWith("/*") _ Or text.Text.StartsWith(" *") _ Or text.Text.StartsWith("//") _ Or text.Text.StartsWith("'") text.LineDown( ) text.StartOfLine( ) text.WordRight(True, 2) End While text.StartOfLine( ) text.WordRight(True) Dim lineTarget As Integer = text.AnchorPoint.Line Dim correctPlace As Boolean = False While text.Text.StartsWith(keyword) And _ Not alreadyUsed And Not correctPlace Dim startpt As EditPoint = text.BottomPoint.CreateEditPoint( ) Dim endpt As EditPoint = text.BottomPoint.CreateEditPoint( ) endpt.EndOfLine( ) If DTE.ActiveDocument.Language = "CSharp" Then 'Skip ';' endpt.CharLeft( ) End If Dim currentText As String = endpt.GetText(startpt) If currentText = t.Namespace Then alreadyUsed = True ElseIf (currentText < t.Namespace) Then text.LineDown( ) text.StartOfLine( ) text.WordRight(True) Else correctPlace = True lineTarget = text.AnchorPoint.Line End If End While text.StartOfLine( ) If alreadyUsed Then DTE.StatusBar.Text = "Namespace " & t.Namespace _ & " is already imported." DTE.StatusBar.Highlight(True) text.MoveToLineAndOffset(line, 1) Else If (correctPlace) Then text.GotoLine(lineTarget) DTE.UndoContext.Open("Add Namespace Directive") text.Insert(keyword & " " & t.Namespace _ & suffix & vbCrLf) DTE.UndoContext.Close( ) DTE.StatusBar.Text = "'" & keyword & " " _ & t.Namespace _ & suffix & "' added to the document." DTE.StatusBar.Highlight(True) text.MoveToLineAndOffset(line + 1, 1) End If text.EndOfLine( ) End If End Sub End Module
All you need to do is download or transcribe this code into the Macro IDE [Hack #51] . You will then see the two macros in the Macro Explorer and can start using them right away.