Under the Hood of C# Alias Types and Namespaces
C# has two different ways to refer to a lot of its fundamental types. String or string, Boolean or bool, Int32 or int, check out the entire list.
What’s the difference? Why does C# provide two ways to reference the same thing?
If you come from the Java world you might immediately think primitive v. object.
I thought that for a long time, but that’s not what C# is doing. In fact, string isn’t even a type. It just an alias for System.String.
Whenever you use string it’s equivalent to typing System.String. Same thing goes for all the other alias types.
So what benefit do you the developer gain from alias types? Why should you use them? What problems might you encounter if you don’t use them?
To answer those questions lets take a look at some examples and discuss C# namespaces.
The first example is very clear. This is how we all use strings, everything compiles and the string is printed to the console. Nothing complicated about that.
We’ve changed from string to String, but the program runs exactly the same as exampleOne.
Now things get interesting. I’ve defined a new class named String inside of the SolarSystem namespace and now my program doesn’t compile. C# seems to be confused with the introduction of the new String class.
I’ve provided the full namespace to the real String class and it works again. So confusion with SolarSystem.String must have been the reason exampleThree broke.
We revert back to lowercase string and it works again. The alias type prevents C# from getting confused with the SolarSystem.String class. A plus for using alias types! No confusion!
Lots of changes! We added a new namespace, moved our using statement inside of our new namespace and removed the System from the String reference. Now it works again! Even though SolarSystem.String class still exists, the relocation of the using statement changed how C# picked which String class to use.
The reason this works boils down to how C# resolves class names, which we’ll dive into after one more example.
Here we use what’s called a using alias directive_. S_ince we have two classes named String, we’re creating a local alias in order to avoid ambiguity between the two.
Admittedly System.String v RealString doesn’t save that much typing. This is more helpful when referencing classes that have long namespaces. But it’s another tool to use when trying to avoid class name ambiguity.
To recap our examples, not using alias types opens the door to your program breaking if someone else wants to create a String or Boolean type.
Admittedly most people aren’t going to do that, but the problem is they could, and if you use String there’s nothing you can do to stop them. Because of that, I would suggest always using alias types to avoid confusion.
Understanding namespaces will help you as a developer avoid some of these issues. To understand what’s happening with exampleSix and exampleSeven lets discuss more about C# namespaces and how C# resolves class references.
Everything in C# exists in a namespace, even if it’s the global namespace. Namespaces help organize projects and typically their names describe what kind of code is contained within them.
What do you think is in the System.Collections.Generic namespace? Generic Collections!
What is in the System.IO namespaces? Code helping deal with Input/Output needs.
C# must be able to locate your classes in order to compile and run your program. You can either provide the full namespace to your class or let C# find it itself.
That’s what happened in exampleThree, we didn’t specify which namespace our String object was in. So C# went looking and found one that broke our program.
If we always wanted to avoid namespace conflicts, every class reference would include the full namespace. That’s how we fixed our problem in exampleFour. We included the full namespace to the real String class. But full namespaces are cumbersome to type, so generally full namespaces are only used if your program would break otherwise.
When namespaces aren’t used, how does C# go about trying to find a class?
C# applies a simple search pattern to locating a class.
- It searches the current namespace and all imported namespaces for the class.
- If it finds a matching class name that’s what it will resolve to.
- Move to the parent namespace and start again at 1.
It repeats these steps until it finds a matching class or runs out of namespaces to search through.
Knowing this, lets revisit some of our previous examples. We’ll annotate how C# decides to resolve namespaces for classes.
Example 2 Revisited
Compiler needs to locate where the String class is defined. It looks through SolarSystem namespace and doesn’t find anything. Moves up to the global namespace, the System namespace is imported and there is a String defined in System. It chooses that string and everything works correctly.
Example 3 Revisited
Compiler needs to locate String. It looks through SolarSystem namespace, finds a String class and uses that one. Unfortunately that class is not the real String class, so a exception is thrown.
It found SolarSystem.String before ever getting to the global namespace which is where System.String is imported. It stops as soon as it finds something, even if that’s not what we wanted.
Example 4 Revisited
Here we provide the full namespace to String, C# doesn’t have to search for the class so everything works as we expect it to.
Example 6 Revisited
Compiler needs to locate String. It looks through SolarSystem.Earth namespace. No String class is defined there but the System namespace is imported and it finds a String in that namespace. Everything works!
Like exampleFour, C# stops as soon as it finds a matching class name. Since we changed where the using statement was we allowed C# to stop searching before finding SolarSystem.String.
Alias types and namespaces are something I use everyday. For a considerable amount of time, I never really understood how they worked. But now you know better and can impress all your C# friends!