Back to Basics - Do namespace using directives affect Assembly Loading?
Jeff Atwood had a great post a while back called "Strong Opinions, Weakly Held." It's good to feel strongly about something, but important to be open to changing your opinion if you're faced with new evidence.
Last week a reader pointed me to this post at the Microsoft StyleCop blog that shows some interesting examples of using directives outside and inside the namespace declaration.
For example, this compiles fine:
using Guid = System.Guid;
namespace Microsoft.Sample
{
public class Guid
{
public Guid(string s){}
}
public class Program
{
public static void Main(string[] args)
{
Guid g = new Guid("hello");
}
}
}
However this one with the using moved inside the namespace doesn't compile:
namespace Microsoft.Sample
{
using Guid = System.Guid;
public class Guid
{
public Guid(string s){ }
}
public class Program
{
public static void Main(string[] args)
{
Guid g = new Guid("hello");
}
}
}
The code fails on the following compiler error, found on the line containing Guid g = new Guid("hello");
CS0576: Namespace 'Microsoft.Sample' contains a definition conflicting with alias 'Guid'
In the first example, there's an alias created, but it doesn't matter because the second Guid class is in local scope (there's no scope conflict) and the compiler chooses the inner Guid class.
In the second example, there are two "Guids" declared in the same scope and that's a conflict that the compiler can't resolve automatically. The style rule/argument the post makes is that you will only see these kinds of conflicts if you put your using directives inside your namespaces. To this, I say, "meh." Sure, if it makes you happy and you use lots of namespace aliases, sure, but it's an edge case. I simply prefer to have my namespaces outside.
Read Twice, Test Once
However, the second rule in the post said:
"However, placing the using statements [Ed. Note: They mean "directives"] within a namespace element allows the framework to lazy load the referenced assemblies at runtime. In some cases, if the referencing code is not actually executed, the framework can avoid having to load one or more of the referenced assemblies completely. This follows general best practice rule about lazy loading for performance."
This stopped me in my tracks. This rocks the very bedrock that my knowledge of the CLR stands on. I'm like, NO WAY, and then I oscillated back and forth between denial and acceptance. Then, I settled on denial. I don't buy it. A using directive is for aliasing and is a kind of syntactic sugar. Ultimately the IL is the same. Assembly loading won't be affected as the assembly manifest doesn't change.
Here's what my experiment showed. I believe it's true until I find out from someone on the CLR Loader team that it's not true. ;)
First Test
using System;
using System.Xml;
namespace Microsoft.Sample
{
public class Program
{
public static void Main(string[] args)
{
Guid g = Guid.NewGuid();
Console.WriteLine("Before XML usage");
Console.ReadLine();
Foo();
Console.WriteLine("After XML usage");
Console.ReadLine();
}
public static void Foo()
{
XmlDocument x = new XmlDocument();
}
}
}
I ran this program outside the debugger but compiled in debug mode. At the point there the first ReadLine() hits, the program pauses and waits for an Enter key. I loaded up Process Explorer and saw:
Then, I hit Enter, executing the Foo() method and new'ing up an XmlDocument. You can see that System.Xml just got loaded (specifically the native image) into the process.
Second Test
If I do the same thing with the usings INSIDE the namespace I get identical results.
namespace Microsoft.Sample
{
using System;
using System.Xml;
public class Program
{
public static void Main(string[] args)
{
Guid g = Guid.NewGuid();
Console.WriteLine("Before XML usage");
Console.ReadLine();
Foo();
Console.WriteLine("After XML usage");
Console.ReadLine();
}
public static void Foo()
{
XmlDocument x = new XmlDocument();
}
}
}
In fact, the only thing that changed the way the assemblies got loaded was switching to release mode. Running the app in release mode had all the assemblies in my trivial app loaded immediately. I thought it was weird for a second, but then realized it had nothing to do with debug vs. release. It was simply that the Foo() method was either inlined or there was a Tail Call Optimization as I explored in this post: Release IS NOT Debug: 64bit Optimizations and C# Method Inlining in Release Build Call Stacks.
I'm 99.99% sure at this point that using directives can't change your assembly loading behavior and I think I was right to be suspicious. However, I'm going to ask some people on the Fusion (assembly loader) and C# teams who are smarter than I and I'll update this post as I learn more!
However, the Back to Basics Tips here are:
- Don't believe everything you read, even on a Microsoft Blog.
- Don't believe this blog, either!
- Decide for yourself with experiments if you need a tiebreaker!
And be ready to be wrong anyway! It only takes one negative experiment to disprove a theory. Of course, the real question is, what does the specification say?
UPDATE #1: Ian Griffiths had a similar reaction and a similar test!
What do you think, Dear Reader?
About Scott
Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.
About Newsletter
Rules like this are one reason I've decided that I don't really like stylecop.
I saw the same thing and thought, "no way! That can't be right. That means everybody's been doing it the wrong way for years"
There's just no way everybody would do it wrong that long.
Plus the "wrong" way looks better.
StyleCop documentation also mentions "Note, this is subject to change as the .Net Framework evolves, and there are subtle differences between the various versions of the framework.", so it might be a safeguard to devise a rule to cover different framework versions.
You'll find the position of the using statement has no impact on the IL. (This means you won't need to bother the fusion team.)
The IL and metadata in a .NET assembly doesn't actually have anything corresponding to namespaces. Namespaces are really just a synthetic construct built on top of a naming convention for classes.
Even the msdn help on the using directive:
http://msdn.microsoft.com/en-us/library/sf0df423.aspx
doesn't say anything about *loading* the assembly, it just says it gives you, quote: "To allow the use of types in a namespace so that you do not have to qualify the use of a type in that namespace".
No, I don't buy this.
Regarding the actual problem, for me the directives are just syntactic sugar, really... I didn't check, but what does the IL say?
Its always best thing to know the basics very.
Thanks,
Yash
Imagine a simple scenario where assembly A references assembly B, which implements namespace N. The first using N; statement in assembly A sources would cause loading assembly B, according to that rule.
Now, let's add assembly C, which also implements classes in namespace N. It also implements classes in namespace O as well. Assembly A references C, but uses only classes from namespace O. However, that same using N; statement now should cause the runtime to load assembly C as well.
To make things even worse, we'll add assembly D, which implements namespace P. Assembly C references D and has using P; statement. Even if that statement is inside the namespace N portion, the first use of any class in namespace N implemented by assembly B should cause the assembly D to be loaded as well. After all, code inside namespace N is being executed, so according to the rule explanation this nested using statements should cause loading the assemblies implementing these namespaces. It shouldn't matter that the code excecuted is in assembly B and the using P; statement is inside assembly C; it's in the same namespace N and namespaces are not assembly bound like the types.
On the other hand, if assemblies are being lazy loaded at type resolution, the runtime will have it a lot easier.
Of course, I am not on the CLR or the Fusion teams, so I can't claim I know what I am talking about... :-)
In this condition, the order of declaration of using directives won't change the way assemblies are loaded at runtime. Maybe you could show with reflector that the IL produce is the same, or even better, you could show that the assembly 2 files are identical, bytes per bytes.
Still, the point of the post is the loader claim, which appears specious, rather than the style issues.
using System;
namespace wheretoplaceusing
{
using System.Xml;
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Press enter to load XmlDocument.");
Console.ReadLine();
LoadXml();
Console.WriteLine("XmlDocument created.");
Console.ReadLine();
}
static void LoadXml()
{
XmlDocument xmlDoc = new XmlDocument();
}
}
}
Compiled in debug mode, it ran without loading System.Xml.dll assemblies until I hit Enter.
The IL for the Main method looked like
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// Code size 42 (0x2a)
.maxstack 8
IL_0000: nop
IL_0001: ldstr "Press enter to load XmlDocument."
IL_0006: call void [mscorlib]System.Console::WriteLine(string)
IL_000b: nop
IL_000c: call string [mscorlib]System.Console::ReadLine()
IL_0011: pop
IL_0012: call void wheretoplaceusing.Program::LoadXml()
IL_0017: nop
IL_0018: ldstr "XmlDocument created."
IL_001d: call void [mscorlib]System.Console::WriteLine(string)
IL_0022: nop
IL_0023: call string [mscorlib]System.Console::ReadLine()
IL_0028: pop
IL_0029: ret
} // end of method Program::Main
Code sample #2
using System;
using System.Xml;
namespace wheretoplaceusing
{
class Program1
{
static void Main(string[] args)
{
Console.WriteLine("Press enter to load XmlDocument.");
Console.ReadLine();
XmlDocument xmlDoc = new XmlDocument();
LoadXml();
Console.WriteLine("XmlDocument created.");
Console.ReadLine();
}
static void LoadXml()
{
XmlDocument xmlDoc = new XmlDocument();
}
}
}
Also compiled in debug mode, but System.Xml assemblies get loaded immediately on program launch. The difference? The IL main method got inlined.
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// Code size 48 (0x30)
.maxstack 1
.locals init ([0] class [System.Xml]System.Xml.XmlDocument xmlDoc)
IL_0000: nop
IL_0001: ldstr "Press enter to load XmlDocument."
IL_0006: call void [mscorlib]System.Console::WriteLine(string)
IL_000b: nop
IL_000c: call string [mscorlib]System.Console::ReadLine()
IL_0011: pop
IL_0012: newobj instance void [System.Xml]System.Xml.XmlDocument::.ctor()
IL_0017: stloc.0
IL_0018: call void wheretoplaceusing.Program1::LoadXml()
IL_001d: nop
IL_001e: ldstr "XmlDocument created."
IL_0023: call void [mscorlib]System.Console::WriteLine(string)
IL_0028: nop
IL_0029: call string [mscorlib]System.Console::ReadLine()
IL_002e: pop
IL_002f: ret
} // end of method Program1::Main
It is interesting to note how the mere placement of a Using statement, which I thought was purely for Visual Studio code editing purposes, actually influences the compiler to generate different IL.
I realised a fault in my second code block, where I forgot to remove the redundant XmlDocument line in the Main method, causing the "inlining" when i peeked at the IL. This caused the System.Xml assemblies to be loaded prematurely.
Class definition, class declaration, custom control, debugger visualizer, windows form, inherited form, component class, windows service, interface, user control, installer class
As I recall, in Visual studio 7 there was at least one template that put the usings inside the namespace declaration. I can't remember which one(s). But even then a class definition template put the usings outside the namespace declaration.
I'd consider usings outside the namespace as the standard. The edge case of needing more than one namespace per file can always opt to move the usings inside each namespace when there are differences. Even then I'd put the common ones outside the namespace declaration to reduce redundency.
There is a simple solution to problem 1 (Type Confusion). Don't name your alias the same as any known class name. As a general rule I use abbreviations when creating an alias, and don't use abbreviations when naming a class.
using SWUIWC = System.Web.UI.WebControls;
Seriously though, after falling off my chair, my next thought would have been 'and what if I fully qualify type names - what then?'
System.Xml.XmlDocument x = new System.Xml.XmlDocument();
Comments are closed.