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:
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:
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
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
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
For i = 0 To nodes.Length - 1
For Each nsTemp In lst.Keys
If (strShortestNS.ToLower() <> nsTemp) Then
Dim nodesTemp() As String = CType(lst.Item(nsTemp), String).Split(".")
If (nodesTemp.Length >= i) Then
If (nodes(i) <> nodesTemp(i)) Then
blnContinue = False
Exit For
End If
Else
blnContinue = False
Exit For
End If
End If
Next
If (blnContinue = True) Then
nodeStack.Add(nodes(i))
intCommonNodes += 1
Else
Exit For
End If
Next
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.
|