0

I've got a namespace in a referenced project, Project.Base, and someone thought it would be a good idea to declare their own generic List class in that namespace.

Now I have a code file that starts out:

using System.Collections.Generic;
using Project.Base.Sub;

The Project.Base namespace is never actually formally used; just a nested namespace within it. System.Collections.Generic is formally used. But for some bizarre reason, when I declare a method whose return type is List<T>, it interprets that as a Project.Base.List<T> rather than a System.Collections.Generic.List<T>!

This seems incredibly counterintuitive to me. Does anyone know what's going on and how to fix it?

EDIT To reproduce:

using System;
using System.Collections.Generic;
using Project.Base.Sub;

namespace Project.Base
{
    public class List<T> { }
}

namespace Project.Base.Sub { }

namespace Project.Base.Sub2
{

    public class P
    {
        public static void Main()
        {
            var list = new List<int>(); 
            Console.WriteLine(list.GetType().Namespace);
            Console.Read();
        }
    }
}

(Note that the code in question is in a different sub-namespace of Project.Base. This appears to be significant.)

Mason Wheeler
  • 82,511
  • 50
  • 270
  • 477
  • Could you post the code for the Project.Base.Sub namespace? Or at least the beginning – Casey O'Brien Feb 16 '17 at 01:50
  • In what namespace does the code that references the `List` exist? – John Koerner Feb 16 '17 at 04:14
  • 2
    `namespace Project.Base { public class List{} } namespace Project.Base.Sub {} namespace Foo { using System.Collections.Generic; using Project.Base.Sub; public class P { public static void Main() { var list = new List(); System.Console.WriteLine(list.GetType().Namespace); } } }` Your problem as stated cannot be reproduced. **Post code that actually reproduces the problem**. – Eric Lippert Feb 16 '17 at 06:58
  • @EricLippert \*facepalm\* I bet that's actually the problem. I didn't even think to mention this, but the code referencing `List` is in a different sub-namespace of `Project.Base`. Edited now. – Mason Wheeler Feb 16 '17 at 08:41
  • And now you can remove the `using Project.Base.Sub;` from your repro and see it still exhibits the same behaviour. That `using` declaration is a complete red herring. – Damien_The_Unbeliever Feb 16 '17 at 08:56

2 Answers2

2

Name lookup proceeds by searching up through enclosing namespaces, and only considering the using directives that are in scope of each one.

To make this compile, you can move your using directive inside:

using System;

namespace Project.Base
{
    public class List<T> { }
}

namespace Project.Base.Sub { }

namespace Project.Base.Sub2
{
    using System.Collections.Generic;

    public class P
    {
        public static void Main()
        {
            var list = new List<int>();
            Console.WriteLine(list.GetType().Namespace);
            Console.Read();
        }
    }
}

The top-level using directives are only inside the global namespace, and so would only be searched if lookup in enclosing namespaces fails. So, the first lookup fails to find a Project.Base.Sub2.List, but then at the next level, it located Project.Base.List. If that had failed, it would have tried to locate a Project.List type.

The other using directive was a red herring.

Damien_The_Unbeliever
  • 234,701
  • 27
  • 340
  • 448
2

The accepted answer is correct. Another way to understand the problem is that your posted code is exactly equivalent to

using System;
using System.Collections.Generic;
using Project.Base.Sub;

namespace Project
{
  namespace Base
  {
    public class List<T> { }
    namespace Sub { }
    namespace Sub2 
    { 
      public class P
      {
        public static void Main()
        {
          var list = new List<int>(); 
          Console.WriteLine(list.GetType().Namespace);
          Console.Read();
        }
      }
    }
  }
}

And now it should be clear why List resolves as it does. The declaration of List is directly in an enclosing namespace.

From a design perspective, it would be a good idea for you to push back on your colleague who thinks that naming a class "List" is a good idea. It is often stated that namespaces exist to prevent name conflicts, but that actually is their secondary purpose. In realistic code there usually are not very many naming conflicts, because people generally do not do confusing things like name their own class "List".

The primary purpose of namespaces is to organize code into logical groups, so that you can use using to "pull into scope" the names of the things you're likely to use in your program. This lets IntelliSense do a better job of figuring out what you mean as you're typing, makes the code easier to read, and so on. If you find that you're using namespaces to prevent naming conflicts, you might want to consider whether those conflicts could be eliminated by some tactical renamings instead.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • Is what you posted actually valid? I had no idea the C# language allows you to nest `namespace` blocks. (99.99% of the code I've seen uses one `namespace` block to enclose the entire file, with the possible exception of comment headers and `using` statements, and the rest is either tests or examples.) – Mason Wheeler Feb 16 '17 at 15:03
  • 1
    @MasonWheeler: The C# specification clearly states that `namespace Foo.Bar { } ` is *exactly* the same as `namespace Foo { namespace Bar { } }`. The code is valid; I encourage you to run it if you have doubts. I note that I would not consider this code to be *good style*, but it is *valid*. – Eric Lippert Feb 16 '17 at 15:17
  • @MasonWheeler: If you are interested in learning more facts about namespaces that you do not yet know, you might want to read https://blogs.msdn.microsoft.com/ericlippert/tag/namespaces/ – Eric Lippert Feb 16 '17 at 15:18
  • Class with the same name as the namespace? Ouch! [Insert "why would you do that" meme here] – Mason Wheeler Feb 16 '17 at 16:09
  • @MasonWheeler: This question might also be of interest to you: http://stackoverflow.com/questions/2046012/companyname-foo-is-a-namespace-but-is-used-like-a-type/2330632#2330632 – Eric Lippert Feb 24 '17 at 14:08
  • Interesting puzzle. I took one look at it and said "string", then looked again and said, "no, C is nested within a reified generic class, so it means that T in this context is statically bound as the type argument `int` before we even apply a type parameter to `A`." That's not exactly the reasoning you gave in your explanation post, but it feels right. Is that valid? – Mason Wheeler Feb 24 '17 at 14:52
  • @MasonWheeler: In the post where I give the answer I note that almost everyone comes up with that explanation at first. Though it gets you the right answer, it's the wrong mechanism. T means T-from-A, no matter what. The real question is "what does B mean?" – Eric Lippert Feb 24 '17 at 14:58