Do not name a class the same as its namespace, Part One

Do not name a class the same as its namespace, Part One

Rate This
  • Comments 25

(This is part one of a four part series; part two is here.)

The Framework Design Guidelines say in section 3.4 “do not use the same name for a namespace and a type in that namespace”. (*) That is:

namespace MyContainers.List
{
    public class List { … }
}

Why is this badness? Oh, let me count the ways.

Part One: Collisions amongst referenced assemblies:

You can get yourself into situations where you think you are referring to one thing but in fact are referring to something else. Suppose you end up in this unfortunate situation: you are writing Blah.DLL and importing Foo.DLL and Bar.DLL, which, unfortunately, both have a type called Foo:

// Foo.DLL:
namespace Foo { public class Foo { } }

// Bar.DLL:
namespace Bar { public class Foo { } }

// Blah.DLL:
namespace Blah
{
  using Foo;
  using Bar;
  class C { Foo foo; }
}

The compiler gives an error. “Foo” is ambiguous between Foo.Foo and Bar.Foo. Bummer. I guess I’ll fix that by fully qualifying the name:

  class C { Foo.Foo foo; }

This now gives the ambiguity error “Foo in Foo.Foo is ambiguous between Foo.Foo and Bar.Foo”. We still don’t know what the first Foo refers to, and until we can figure that out, we don’t even bother to try to figure out what the second one refers to.

This reveals an interesting point about the design of the “type binding” algorithm in C#. That is, the algorithm which determines what type or namespace a name like “X.Y” is talking about. We do not “backtrack”. We do not say “well, suppose X means this. Then Y would have no meaning. Let’s backtrack; suppose X means this other thing, oh, yes, then Y has a meaning.” We figure out what X unambiguously means, and only then do we figure out what Y means. If X is ambiguous, we don’t check all the possibilities to see if any of them has a Y, we just give up.

Assuming you cannot change Foo.DLL, the correct thing to do here is to either remove the “using Foo” – and who knows what all that will break – or to use an extern alias when compiling:

// Blah.DLL:
extern alias FOODLL;
namespace Blah
{
  using Foo;
  using Bar;
  class C { FOODLL::Foo.Foo foo; }
}

Many developers are unfamiliar with the “extern alias” feature. And many of those developers who end up in this situation thereby end up in a cleft stick not of their own devising. Some of them send an angry and profane email to the person who did not cause the problem in the first place, namely, me.

The problem can be avoided in the first place by the authors of Foo.DLL following the guidelines and not naming their type and their namespace the same thing.

Next time: machine-generated code throws a wrench into its own works.

****************

(*) FXCOP flags violations of a far more stringent guideline – FXCOP will tell you if *any* type name conflicts with *any* namespace. But today I’m just talking about the straight-up type-in-a-namespace scenario.

(This is part one of a four part series; part two is here.)

  • I thought I knew every keyword in the C# language, but I guess I was wrong... I had never heard about "extern alias" (btw, it's not listed on the keywords page on MSDN). Learn something new every day !

  • It's listed in a roundabout way:

    http://msdn.microsoft.com/en-us/library/x53a06bb.aspx

    "alias" is not a keyword as such (it's a "context keyword"), so it's not listed. But "extern" is listed, and if you follow the link, you'll see a reference to "extern alias" right at the top.

  • Why not just make the the C# compiler raise a compiler error if it detects a type inside a namespace with the same name as the parent namespace? Was there a conscious decision to allow it or a reason for it?

  • braddabug...It is allowed because it is legal [although a dangerous practice as this shows]. There are other (more complex) scenarios where this also arises where a top level type in one assembly may match a class name in another assembly. This is impossible to detect at the time where the individual assemblies are compiled.

  • Vaguely related: What about naming a property the same as a type? The compiler usually has no problems distinguishing between the two but avoiding it at any cost often leads to awkwardly-named properties.

  • braddabug...It is allowed because it is legal [although a dangerous practice as this shows]. There are other (more complex) scenarios where this also arises where a top level type in one assembly may match a class name in another assembly. This is impossible to detect at the time where the individual assemblies are compiled.

  • Johannes, Property Names that match Type Names are prevelant throughout the Framework. Just look at most "Control" classes, the Type for a property is most often an exact match for the Property Name.

  • Why is the extern alias necessary?  Couldn't you just use global::Foo.Foo?

    In this particular case that would do, yes. But suppose you really, absolutely wanted to be sure that you were talking to the right thing? Then it might be necessary to go with the extern alias. -- Eric

  • This is interesting and it is also not something I would do. That being said, I'm not having success replicating the error codes. I've created Foo.dll, Bar.dll, Blah.dll, namespaces and classnames all match, used using statements, etc. However, the message I get if trying to erroneously type

    Foo foo = new Foo();

    Is "'Foo' is a 'namespace' but is used like a 'type'"

    However, using Foo.Foo fooFoo = new Foo.Foo() and Bar.Foo barFoo = new Bar.Foo() compiles and runs without fail.

    ...Ah... as I'm typing this, I went back and compared one more time. The using statement inside the namespace generates the errors contained in your post. However, I had the using statements outside of the namespace declaration.

    using Foo;
    using Bar;
    namespace Blah
    {
       public class Blah
       {
           Foo.Foo fooFoo = new Foo.Foo();
           Bar.Foo barFoo = new Bar.Foo();
       }
    }

    So that sparks another question, is it a standard practice to include the usings within the namespace rather than at the top of a code file?

  • Thomas: extern alias is mentioned on MSDN (http://msdn.microsoft.com/en-us/library/ms173212(VS.80).aspx). Notice that you actually have to compile the alias "reference" into the assembly. The most common scenario is when you are stuck having to use two different versions of an assembly. Rare, but possible if you do not properly branch source.

    Peace and Grace,

    Greg

    Twitter: @gbworld

  • Foo is rather ambiguous too....  Bar none - Foo foo fooey.

    I wish those terms would be replaced by something that you might truly model, even abstractions are based in reality.

  • Where can I read The Framework Design Guidelines?

    Were you to type that into Bing, it would tell you that you can buy bound copies at Amazon.com, or read them online at http://msdn.microsoft.com/en-us/library/ms229042.aspx. -- Eric 

  • Eric, I'm confused by the behavior Anthony P describes.  According to the old post you linked to,

    using Foo;
    using Bar;

    should be equivalent to

    using global::Foo;
    using global::Bar;

    regardless of whether the using directives are inside or outside the namespace Blah (*unless* Blah contains namespaces called Foo and Bar).

    So why does the error depend on where the usings are located?

    The C# specification does not specify what the error messages should say, just whether there should be errors or not. The compiler uses various heuristics to try to figure out the "best" error for a given situation. Some of those heuristics are sensitive to small changes. I'm not sure exactly what the heuristic is for this situation, but I was recently looking at the code that gives errors when a type binding fails. The heuristic there is very complicated; a binding could fail because the result was inaccessible, ambiguous, "bogus" (that is, the winner is a type imported from metadata that is so malformed that the C# compiler cannot actually use it), of the wrong generic arity, and so on. Or any combination of the above! It's hard to say what the best error message is for a given situation; we do our best, but sometimes do end up producing confusing error messages. -- Eric

  • Been there, done that, and it's worse when the VS Designer is involved:

    http://stackoverflow.com/questions/1259399/alias-and-namespace-conflict-in-visual-studio-designer

    That's what I'll be discussing next time. -- Eric

  • But Eric, it seems that Anthony P is saying (and I admit I haven't tested it) that by putting the using directives outside his namespace and by writing "Foo.Foo foo" instead of just "Foo foo" that he can get it to compile and run without any errors at all.  How is that possible? (or perhaps I'm misunderstanding what is being said)

    Oh, I see what you mean. Yes, that produces no error and the lookup becomes unambiguous then. The "using Foo" is effectively ignored. The lookup rules, briefly are "look in yourself, look in your base, look in your outer namespace, look in your outer namespace's using directives". 

    If the "using Foo" is moved to the global namespace then we look in the global namespace for Foo first and find namespace Foo; we never even consider the using directive. If the "using Foo" is inside the declared namespace then we check the using clause before we check the global namespace.

    This is a subtle difference. With the using directive in one place, Foo means the namespace, in the other, it means the type. This is yet another reason to avoid this pattern: because it changes the bindings of things in strange and subtle ways when you make changes that you think ought to be no-ops, like moving a using directive outwards or inwards. -- Eric

Page 1 of 2 (25 items) 12