C# 2.0 Aliases






4.35/5 (23 votes)
Oct 3, 2005
6 min read

76007
A general description of C# 2.0 aliases.
Introduction
Undeniably, the discussion on aliases cannot be tackled without a quick history on their scope of use. Of course, to attack this subject, a brief lecture on namespaces is necessary so here it goes. C# namespaces allow you to create a system to organize your code in a hierarchical manner. An example of this is given below:
namespace Apresss
{
namespace Data
{
public class DataManager
{
}
}
namespace IO
{
public class BluetoothReader
{
}
}
}
The items within can either be accessed via the fully qualified type name, as illustrated in the example below:
Apress.Data.DataManager dman = new Apress.Data.DataManager();
Or, by explicitly scoping to items of that namespace with the using
declaration. An example of this is given below:
using Apress.IO;
class Program
{
static void Main(string[] args)
{
Apress.Data.DataManager dman = new Apress.Data.DataManager();
BluetoothReader btooth = new BluetoothReader();
}
}
The only purpose of the using
command in this context is to save you typing and make your code simpler. It does not, for example, cause any other code or libraries to be added to your project. Besides providing us with the ability to permit types in a given namespace without qualification, the using
keyword can also provide us the ability to create aliases for a namespace. An example of this is provided below:
using aio = Apress.IO;
and the ability to provide aliases for types as in the example below:
using Apress.IO;
using aio = System.Console;
class Program
{
static void Main(string[] args)
{
aio.WriteLine("Aliased Type.");
}
}
In the above example, the type System.Console
has been given a new name aio
. The code inside the program’s Main
method can now access members of Console
through this alias.
The most common use of aliases is in situations where name collisions occur between two libraries, or when a small number of types from a much larger namespace are being used. Examine the sample listed below:
namespace Apress
{
namespace Data
{
public class DataManager
{
}
public class BluetoothReader
{
}
}
namespace IO
{
public class BluetoothReader
{
}
}
}
The namespaces Apress.Data
and Apress.IO
have types with the same name. Applying the using
directive to access type members in both namespaces and then trying to instantiate a BluetoothReader
will create an ambiguity that cannot be resolved hence the code listed below will not compile:
using Apress.IO;
using Apress.Data;
class Program
{
static void Main(string[] args)
{
BluetoothReader bt = new BluetoothReader();
}
}
What’s new to aliases?
Aliasing namespaces appear to be an adequate solution in the example listed above. The sample illustrates once again how this works:
using apressio = Apress.IO;
using apresssdata = Apress.Data;
class Program
{
static void Main(string[] args)
{
apressio.BluetoothReader bt =
new apressio.BluetoothReader();
}
}
Unfortunately, there are situations where this practice would produce errors. The problem rears its ugly head when namespace names within the referenced libraries collide with our given alias names. The example below illustrates this problem:
namespace apressio
{
public class TypeB
{
}
}
namespace apressdata
{
public class TypeA
{
}
}
namespace Apress
{
namespace Data
{
public class DataManager
{
}
public class BluetoothReader
{
}
}
namespace IO
{
public class BluetoothReader
{
}
}
}
In the example above, two new namespaces apressio
and apressdata
have been added, because they share common names with our aliases the code will not be allowed to compile. A quick solution to the problem is to move the alias definition out of module scope and into the namespace where they are used. The example below illustrates the initial problem:
using apressio = Apress.IO;
using apressdata = Apress.Data;
namespace apressio
{
public class TypeB
{
}
}
namespace apressdata
{
public class TypeA
{
}
}
namespace Apress
{
namespace Data
{
public class DataManager
{
}
public class BluetoothReader
{
}
}
namespace IO
{
public class BluetoothReader
{
}
}
}
namespace Aliases
{
class Program
{
static void Main(string[] args)
{
//apressio.Bluetooth
apressio.BluetoothReader bt =
new apressio.BluetoothReader();
}
}
}
Here the aliases apressio
and apressdata
are defined in the typical location at the beginning of the C# file. Attempting to compile this code will fail. In order to get it to compile without introducing any new technology, simply move the declarations inside the namespace where they are used. The Aliases
namespace is now defined as follows:
namespace Aliases
{
using apressio = Apress.IO;
using apressdata = Apress.Data;
class Program
{
static void Main(string[] args)
{
//apressio.Bluetooth
apressio.BluetoothReader bt =
new apressio.BluetoothReader();
}
}
}
This strategy presents its own set of issues; summarily we find that the new namespaces are no longer visible within that scope. The namespace alias qualifier, introduced in C# 2.0, is the appropriate measure given the scenario highlighted. The example below shows how this can be accomplished:
using apressio = Apress.IO;
using apressdata = Apress.Data;
using aio = apressio;
namespace apressio
{
public class TypeB
{
}
}
namespace apressdata
{
public class TypeA
{
}
}
namespace Apress
{
namespace Data
{
public class DataManager
{
}
public class BluetoothReader
{
}
}
namespace IO
{
public class BluetoothReader
{
}
}
}
namespace Aliases
{
class Program
{
static void Main(string[] args)
{
apressio::BluetoothReader bt =
new apressio::BluetoothReader();
aio::TypeB tb = new aio::TypeB();
}
}
}
The namespace alias qualifier can be used as follows:
<Left operand> :: < right operand>
where left operand can be a namespace alias, an extern
, or the global identifier. Right hand operand must be a type. Using the namespace alias qualifier with an alias that references a type causes a compile-time error.
global
global is not a keyword or identifier, but when used in tandem with the namespace alias qualifier, the global
namespace alone is searched for the right hand identifier. The global
identifier can be used as follows.
global::apressio.TypeB b = new apressio.TypeB(); //use of global
global::System.IO.DirectoryInfo info =
new System.IO.DirectoryInfo("c:/");
extern
Believe it or not, problems are still lurking in the woodwork. Common sense and a strict compiler prevent us from creating situations like the one defined below where two distinct types share the same qualified name within the same assembly:
namespace apressio
{
public class TypeB
{
}
}
namespace apressio
{
public class TypeB
{
}
}
There is; however, nothing that prevents this exact scenario in two distinct assemblies.
//Apress1.dll
namespace apressio
{
public class TypeB
{
}
}
Apress2.dll
namespace apressio
{
public class TypeB
{
}
}
This of course, isn’t a problem until we run into a situation where both assemblies are referenced. Since all referenced assembly types along with all types in the running program are loaded into the same single namespace hierarchy, when we try to build our consuming program, everything breaks. The scenario described is an all too real problem in the modern day programming world, if not for any other reason than great minds thinking alike. (Referencing multiple versions of the same assembly can cause this as well). Fortunately, with the release of C# 2.0 comes the facility to handle multiple namespace hierarchies, implemented through the extern
keyword and compile time configuration.
Utilizing extern
aliases is a two step process. The first is to declare the hierarchies in our code using the extern
alias keyword. The sample below illustrates.
extern alias ApressLibrary1;
extern alias ApressLibrary2;
extern
alias declarations must not be preceded by anything else so these would go at the top of the file where they are to be used.
extern alias ApressLibrary1;
extern alias ApressLibrary2;
using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
using apressio = Apress.IO;
using apressdata = Apress.Data;
using aio = apressio;
namespace Aliases
{
class Program
{
static void Main(string[] args)
{
//code that references two seperate TypeB
//definitions
//will go here
}
}
}
The next step can be done in two different ways. From the command line we would define the aliases during compilation as follows:
[Editor comment: Line breaks used to avoid scrolling.]
csc /r:ApressLibrary1= ApressLibrary.dll /
r: ApressLibrary2= ApressLibrary2.dll Aliases.cs
This step can also be achieved using Visual Studio 2005 integrated development environment by modifying the Aliases
property of the referenced assembly, which always defaults to global, to the values ApressLibrary1
and ApressLibrary2
for ApressLibrary1.dll and ApressLibrary2.dll respectively. The referenced assemblies properties tab can be reached by viewing the standard properties window while selecting a referenced assembly. The diagram below shows the initial Alias of a referenced assembly:
For ApressLibrary1
the properties tab would be modified to this:
And for ApressLibrary2
the properties tab would be modified to this:
We now have two root namespace hierarchies that can be accessed uniquely. The code below illustrates their use:
extern alias ApressLibrary1;
extern alias ApressLibrary2;
using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
namespace Aliases
{
class Program
{
static void Main(string[] args)
{
ApressLibrary1::apressio.TypeB typeB =
new ApressLibrary1::apressio.TypeB();
ApressLibrary2::apressio.TypeB typeB2 =
new ApressLibrary2::apressio.TypeB();
}
}
}
The example above defines the extern
alias ApressLibrary1
to be the root of a namespace hierarchy formed by the types in ApressLibrary1.dll and ApressLibrary2
to be the root of a namespace hierarchy formed by the types in ApressLibrary2.dll. TypeB
can now be accessed using the namespace qualifier syntax without name collisions.
Whereas the command line alias declaration is achieved by utilizing the /r switch multiple times, (to reference the same assembly with multiple aliases for instance), Visual Studio 2005 allows for multiple aliases to be presented by accepting a comma delimited string in the Aliases
property. We could then place an assembly in the global
namespace as well as in an extern
alias and be accessed in either manner. In the example below, the assembly ApressLibrary2.dll is referenced into the global
namespace and into the ApressLibrary2
namespace.
The TypeB
found in apressLibrary2.dll can now be accessed through the ApressLibrary2
extern
alias or through the global
namespace alias. The example below illustrates:
extern alias ApressLibrary1;
extern alias ApressLibrary2;
using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
namespace Aliases
{
class Program
{
static void Main(string[] args)
{
ApressLibrary1::apressio.TypeB typeB =
new ApressLibrary1::apressio.TypeB();
ApressLibrary2::apressio.TypeB typeB2 =
new ApressLibrary2::apressio.TypeB();
apressio.TypeB typeB3 = new apressio.TypeB();
}
}
}
Once an extern
alias is defined, it can be used as any standard namespace. The example below illustrates:
extern alias ApressLibrary1;
extern alias ApressLibrary2;
using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
namespace Aliases
{
using ApressLibrary1::Apress.Data;
using io = ApressLibrary2::apressdata.TypeA;
class Program
{
static void Main(string[] args)
{
ApressLibrary1::apressio.TypeB typeB =
new ApressLibrary1::apressio.TypeB();
ApressLibrary2::apressio.TypeB typeB2 =
new ApressLibrary2::apressio.TypeB();
apressio.TypeB typeB3 = new apressio.TypeB();
io typeA = new io();
BluetoothReader blueReader =
new BluetoothReader();
}
}
}