Saturday, December 19, 2009

Referencing Assemblies and Importing Namespaces

I often see people confused about the difference between referencing an assembly and importing a namespace, and why we need to do either. I’ll try to address that confusion here.

First, what is an assembly? An assembly is a .NET executable file, i.e. EXE or DLL. Whenever you build a .NET project, you are compiling your source files into an assembly.

Think of an assembly as a physical collection of .NET types. A type is a class, structure, enumeration, delegate, etc. An assembly is a physical file that contains the definitions of one or more types. You can move that assembly around and thereby move those types around. I can create an assembly here and then pass you the file and you can then use the types it contains.

Now, by default, each project pretty much only knows about the types for which it contains the definitions. Such a project would be useless though. There are certain parts of the .NET Framework that are pretty much essential to all projects. The assemblies containing these essential types, e.g. String and Int32, are referenced by default in every project.

To see this, create a new project in Visual Studio. Under your preferred language in the New Project dialogue, choose Windows and then select the Empty Project template. Once the project is created, open the Object Browser window and select My Solution in the Browse field at the top. Each root node in the tree is an assembly that your project knows about. You’ll see your project itself and mscorlib at least. If you’re targeting .NET 3.5 or later you will also see System.Core and if you’re language is VB then you will also see System and Microsoft.VisualBasic. These are the assemblies that your project references by default. Basically, there’s a line written in a file in your project that says “mscorlib.dll exists and this is where to find it”.

At this point, your project knows about ALL those assemblies and ONLY those assemblies, so it knows about ALL the types declared in those assemblies and ONLY the types declared in those assemblies. Any type that isn’t declared in one of those assemblies is unknown to your project and therefore unusable. Let’s say, for instance, that you wanted to connect to a SQL Server database. For that you would need to use the SqlConnection class. That class is not declared in any of the assemblies your project references by default so your project doesn’t know that the class exists. The SqlConnection class is declared in the System.Data.dll assembly, so we must reference this assembly in our project in order to use the SqlConnection class. Let’s now look at how to do that and how the process differs between VB and C# projects.

In C#, references are displayed in the Solution Explorer by default. Open the Solution Explorer and expand the node for your C# project. Try to expand the References node and notice that you can’t, which is because it contains no references. The default references of mscorlib.dll and System.Core.dll are considered so fundamental that they are not listed with other references, so that you cannot remove them. Right-click the References node and select Add Reference to open the Add Reference dialogue. Alternatively, select Project -> Add Reference from the main menu.

In VB, references are not displayed in the Solution Explorer by default. You must first click the Show All Files button in the Solution Explorer to reveal the References node, at which point you can right-click it as mentioned above. VB also supports adding a reference from the Project menu, as well as a third option that is not available to C# developers. You can first open the project properties, by double-clicking the My Project node in the Solution Explorer or any of a variety of other ways, and then select the References page. The top half of the page lists the assemblies referenced by the project. Click the Add button under the list to open the Add Reference dialogue.

Once you have opened the Add Reference dialogue, you have several options. The dialogue layout changed considerably between VS 2008 and 2010 but the options available are basically the same. The most common option will be to reference an assembly installed in the Global Assembly Cache (GAC). This includes any assemblies from the .NET Framework itself and any other .NET components that you have installed. The GAC is a location for common assemblies, so that there is only one copy for all projects that use them. You also have the option to browse for an assembly. You would do this for libraries that you have previously created in other projects as well as .NET components that you have downloaded but have no installation routine. Such assemblies will be copied into the output folder of each project that references them. A third option is to reference other projects in the same solution. Only projects that output a library, i.e. DLL, can be referenced. Referencing a project is like referencing the assembly that it outputs except that it allows you to update the assembly without having to recreate the reference.

That’s all there is to referencing assemblies: open the Add Reference dialogue and navigate to the desired assembly. That’s all that’s required to use a type declared in an external assembly. If you ever need to use a type from the .NET Framework but you don’t know where it’s declared, simply open the MSDN documentation for that type. At the very top of every type’s overview topic is the assembly that it’s declared in. Reference that assembly and you’re good to go.

Immediately under the assembly is the namespace that the type is a member of. You never actually need to import a namespace to use a type. Importing namespaces is simply a convenience that makes code easier to write and read. Let’s now look at what a namespace is, as well as how and why you might import one.

Unlike an assembly, which is a physical collection of types, a namespace is a logical collection of types. That means that, while every type is a member of a namespace, a namespace has no physical boundary. You can point to a DLL file and say “there’s the SomeAssembly.dll assembly” but you can never point to a namespace because it doesn’t physically exist.

The reason a namespace is called a namespace is because it is a logical space within which every type name is unique. For instance, there are several classes named TextBox in the .NET Framework alone, not to mention all those that developers around the world have created. When you refer to the TextBox type in code, how do you know which one you’re referring to? You know because each type is qualified by its namespace. For instance, there is the System.Windows.Forms.TextBox class and there is the System.Web.UI.WebControls.TextBox class, plus others besides. It’s similar to there being two people named John at your work place but you know which one someone is talking about because they qualify the name, e.g. John Smith and John Williams or Big John and Little John.

Now, it’s not strictly true that every type in the same namespace must be unique. There’s nothing to stop me declaring the MyControl class as a member of the MyControls namespace and someone else doing the same on the other side of the world. In fact, there’s nothing to stop me doing it in two different projects on the same machine, or even in the same solution. What is true is that you can never have two types with the same name in the same namespace in the same project. That will cause a compilation error.

We don’t actually have to import any namespace to use its member types. You can happily use the fully qualified name of every type in your code, e.g.

string str = ((System.Windows.Forms.TextBox) sender).Text;
Dim str As String = DirectCast(sender, System.Windows.Forms.TextBox).Text
If you do that though, your code will tend to look rather cluttered and will be less comfortable to read. Generally speaking, you should import the namespaces containing the types you use and use the unqualified type names in your code. So, if you import the System.Windows.Forms namespace, the code above can be replaced with the following:

string str = ((TextBox) sender).Text;
Dim str As String = DirectCast(sender, TextBox).Text
Note that, even though the String class is a member of the System namespace, there’s never a need to use System.String, even if you haven’t imported the System namespace. That’s because string (C#) and String (VB) are native data types that you can use to alias the .NET System.String class. The same goes for the int (C#) and Integer (VB) data types and the System.Int32 structure, amongst others.

Getting back to namespaces, how exactly do we import one? There is only one way in C# and that is with the using directive. At the top of a code file you specify a namespace preceded by the using keyword. For instance, to be able to use the WinForms TextBox class unqualified, as was done above, we must import the System.Windows.Forms namespace:

using System.Windows.Forms;
When you create a new code file in C#, you’ll normally find a few using directives added by default. Which ones they are depends on the item template used to create the file. When you create a form in a WinForms project, the System.Windows.Forms namespace is one of those imported by default, because its use is expected to be very common. Note that a using directive applies only to the file it appears in.

Importing a namespace in VB can be similar, except that you use the Imports keyword. The equivalent of the C# using directive above would be:

Imports System.Windows.Forms
When you create a code file in VB, notice that there are no Imports statements added by default, yet you are still able to refer to many types unqualified. This is because VB provides a second option for importing namespaces and it’s that method that’s used by default. Going back to the References page of the project properties, notice that the bottom half of the page contains a list of namespaces. That list contains all the namespaces that have members declared in your project and the assemblies referenced by your project. You simply have to check the box next to a namespace to import it, and some are already checked by default.

Importing a namespace on the References page differs from using the Imports keyword because it is project-wide. Just like the using directive in C#, the VB Imports keyword affects only the code file it appears in, while the References page imports namespaces for every code file in a project.

So, what are my recommendations for importing namespaces? I used the following rules when I was writing all my code myself:
  1. If a type appears only once in code, use the fully-qualified name.
  2. In VB, import a namespace project-wide by default.
  3. In VB, choose file-level imports over project-wide imports if you want to refer to two like-named types from different namespaces in two different code files.
  4. In the case of a name clash, i.e. using two like-named types in the same code file, use the fully qualified name of both types.
Now that I use ReSharper, those rules have changed a little bit because, when an unqualified name is typed, ReSharper will offer to create a file-level import of the appropriate namespace in both C# and VB. Other code-generation tools may be similar.

Another notable difference between VB and C# is support for partial qualifying namespaces. For instance, let’s say that you have imported the System namespace. In C#, if you want to refer to a type in a child namespace, you must still use the fully-qualified name, e.g.

var field = new System.Windows.Forms.TextBox();
In VB, on the other hand, you can use a partially-qualified name, omitting the imported System namespace:

Dim field As New Windows.Forms.TextBox
So, in summary, you must reference an assembly, i.e. a DLL, if you want to use any type declared within it. Importing a namespace is never necessary and simply allows you to use types that are members of that namespace without qualifying the name. If you ever want to use a type but you don’t know what assembly it’s declared in or what namespace it’s a member of, simply consult the MSDN documentation for that type. It will display both that the very top of the page.


Craig said...

At last i understand this. very concise explanation. Thankyou for taking the time to explain these concepts.

Anonymous said...

Fabulously simple explanation. Thanks!

Philip said...

Thanks for the clarification!