...where sanity comes to die.
Visit my blogBlur the lines between genius, insanity, and utter stupidity.WALDOLand Music CentralDevelopment WorkAbout MeContact MeWALDOLand Site Map
 
Infer the default namespace for an assembly
Using Reflection to infer the "default namespace".
Author: WALDO
Publish Date: Sep 25, 2005
Categories: VB.Net, C#, Visual Studio 2003

What is a Default Namespace, anyway?

Let's first talk about the behavioral differences in how Default Namespaces are used in VB.Net versus C# projects.

In VB.Net, the default namespace is prepended to every type and resource in your assembly when compiled. Create a new VB Class Library project called Sample, change the default namespace to MayoSolutions.Demo, and add 2 classes; Class2.vb and Class3.vb (Class1.vb will have already been created by default). When you compile, you will have the following types in your assembly:

Visual Studio Class Viewer MayoSolutions.Demo.Class1
MayoSolutions.Demo.Class2
MayoSolutions.Demo.Class3

Add a namespace directive to Class2, Namespace foo. After compilation you will now have:

MayoSolutions.Demo.Class1
MayoSolutions.Demo.foo.Class2
MayoSolutions.Demo.Class3

Every type continues to be prepended with the current default namespace, MayoSolutions.Demo.

In C#, the default namespace is used as a guide for creating namespaces in code at design-time. Since in C#, namespaces must be explicitly declared, the Default Namespace is not prefixed to types or resources in your assembly. Let's try the same exercise in C#. Create a new C# Class Library project called Sample. You will see here that Class1.cs has already been created for you. Notice the it already has an explicit namespace directive, Sample. Compile here and you will see:

Sample.Class1

Change the Default Namespace to MayoSolutions.Demo. Add two classes; Class2.cs and Class3.cs. Notice the namespace directives on the two classes. They are both MayoSolutions.Demo. After compilation, you should now see:

Visual Studio Class Viewer Sample.Class1
MayoSolutions.Demo.Class2
MayoSolutions.Demo.Class3

But wait, there's more with C#. When adding a class or resource to a subfolder in your project, C# appends the name of the folder to the default namespace. For example, add a folder named 'foo' to your project. Add another class to the 'foo' folder; Class4.cs. Notice the namespace directive on the new class. It is MayoSolutions.Demo.foo. After compilation, you should now see:

Sample.Class1
MayoSolutions.Demo.Class2
MayoSolutions.Demo.Class3
MayoSolutions.Demo.foo.Class4

Do you now see the differences in how the Default Namespace is used in VB.Net versus C# projects? VB.Net prepends the default namespace to every type and resource in the project when it is built. C# uses the default namespace as a template for namespace directives.

That being said, I'm sure you're asking, "Well how do I get the default namespace out of the assembly?"

Inferring the default namespace

There is no property or attribute that will give you the "Default Namespace" from an assembly.

Wait...what?

That's right. Think about it. In C#, namespaces are explicitly defined in the cs files. The Default Namespace on the project has no real bearing on the types inside the assembly when it is built. In VB.Net, it is merely a convenience that the compiler prepends the Default Namespace on the project to the types in the assembly. It just saves VB developers from having to type Namespace directives. There simply is no such animal as Default Namespace when it comes to an assembly. Default Namespace is a concept that applies to projects, not assemblies.

But there's hope.
(Otherwise I wouldn't be writing this long-winded article)

We are going to infer the Default Namespace from an assembly. That means that we are going to take the best guess at what the default namespace is based on type information and resource information available to us.

I enumerate the types and resources within an assembly to get a distinct list of Namespaces. I then take the shortest of the namespaces and look for the common Namespace nodes among the list of distinct Namespaces.

So let's see how this works. We'll use the VB Sample app we previously created. Let's add a namespace directive to Class3.vb (Namespace my.SubNamespace). This distinct list of Namespaces should yield:

MayoSolutions.Demo.Class1
MayoSolutions.Demo.foo.Class2
MayoSolutions.Demo.my.SubNamespace.Class3

This VB.Net code snippet is from a function named InferDefaultNamespace, which accepts a parameter of type System.Reflection.Assembly, named asm.

Public Shared Function InferDefaultNamespace(ByVal asm As [Assembly]) As String
    Dim t As Type
    Dim ns As String
    Dim lst As New Specialized.StringDictionary

    ' Get all types in the assembly and add their namespaces to the list
    For Each t In asm.GetTypes()
        If (Not lst.ContainsKey(t.Namespace.ToLower())) Then
            lst.Add(t.Namespace.ToLower(), t.Namespace)
        End If
    Next
    ...
End Function

So far, lst contains the following Namespaces:

MayoSolutions.Demo
MayoSolutions.Demo.foo
MayoSolutions.Demo.my.SubNamespace

From this, we can see that the Default Namespace should be MayoSolutions.Demo. Let's get the shortest Namespace as a starting point.

...
    Dim strShortestNS As String
    Dim nsTemp As String
    
    ' Get the shortest namespace as a starting point.
    ' We are looking for common nodes in the namespaces, so
    ' it is only logical to start with the shortest one.

    For Each nsTemp In lst.Keys
        If ((strShortestNS = Nothing) AndAlso (nsTemp <> Nothing)) Then
            strShortestNS = String.Copy(lst.Item(nsTemp))
        End If
        
        If (nsTemp.Length < strShortestNS.Length) Then
            strShortestNS = String.Copy(lst.Item(nsTemp))
        End If
    Next
...

We get the shortest Namespace, then compare it to the rest of the Namespaces in the assembly. Why? I want to get the greatest Namespace common to all the classes. Picture this. I have a VB Project. The default namespace is MayoSolutions.Demo. I have three classes, all with namespace directives. Class1 with Namespace Utils, Class2 with Namespace SystemAdmin, and Class3 with Namespace Utils.foo. These classes should yield the following namespaces:

MayoSolutions.Demo.Utils
MayoSolutions.Demo.Utils.foo
MayoSolutions.Demo.SystemAdmin

I want to pull MayoSolutions.Demo, which is the common namespace among all three namespaces. The shortest of these will be the most likely candidate. The longest one most likely have more nodes than any other namespace.

...
    Dim i As Integer
    Dim nodes() As String = strShortestNS.Split(".")
    Dim nodeStack As New ArrayList
    Dim blnContinue As Boolean = True

    ' Begin comparing nodes:
    ' For each node on the shortest namespace...

    For i = 0 To nodes.Length - 1
        ' Compare it to the corresponding node in the other namespaces.
        For Each nsTemp In lst.Keys
            ' There is no point in comparing the shortest namespace to itself.
            If (strShortestNS.ToLower() <> nsTemp) Then
                ' Get the nodes of the namespace being compared.
                Dim nodesTemp() As String = CType(lst.Item(nsTemp), String).Split(".")
                If (nodesTemp.Length >= i) Then
                    If (nodes(i) <> nodesTemp(i)) Then
                        ' Has a different node. Nodes are no longer common. Exit for.
                        blnContinue = False
                        Exit For
                    End If
                Else
                    ' Has less nodes. Shouldn't happen, but if it does, exit for.
                    blnContinue = False
                    Exit For
                End If
            End If
        Next


        ' If the node was acceptable
        If (blnContinue = True) Then
            ' Add it to the list of nodes of the common namespace
            nodeStack.Add(nodes(i))
            intCommonNodes += 1
        Else
            ' Otherwise quit comparing
            Exit For
        End If
    Next


    ' Assemble nodes to form greatest common Namespace.
    ns = String.Join(".", CType(nodeStack.ToArray(GetType(String)), String()))
...

Caveat Emptor

This method for inferring the Default Namespace works very well for me, but since it is an inference, there are some exceptions.

With VB.Net, the default namespace is always prepended to every class, so finding the greatest common namespace could actually yield MORE than you're expecting. Imagine if you created a VB.Net Class Library with 3 classes (Class1.vb, Class2.vb, and Class3.vb). You change the Default Namepsace to MayoSolutions.Demo. Sounds like what we've had before, right? OK, now add Namespace directives to each class (Namespace Extenders.foo). This should yield the following types:

MayoSolutions.Demo.Extenders.foo.Class1
MayoSolutions.Demo.Extenders.foo.Class2
MayoSolutions.Demo.Extenders.foo.Class3

The greatest common namespace among all the classes is MayoSolutions.Demo.Extenders.foo. We were looking for the default namespace of MayoSolutions.Demo.

With C#, the default namespace is just a coding template. This means that conceivably your code colud have NO common namespace. Let's take the C# example from above. The classes from the compiled project should be as follows:

Sample.Class1
MayoSolutions.Demo.Class2
MayoSolutions.Demo.Class3
MayoSolutions.Demo.foo.Class4

Looking at this, we can see that there is no common namespace. Just judging form the logic, it would find the shortest namespace, Sample, then iterate the rest of the namespaces, not finding any matches, and jumping out of the loop.

For best accuracy, ensure your assembly has at least one type or resource that uses the default namespace as the greatest common namespace. This class is useful to me in retreiving resources from a VB-built assembly where I know the name of the resource ahead of time, but not the namespace applied to the resource.