Brian Gideon
Brian Gideon

Reputation: 48969

Can you explain this edge case involving the C# 'using' keyword with namespace declarations and members?

Consider the following short code snippet.

namespace B
{
    public class Foo
    {
        public string Text
        {
            get { return GetType().FullName; }
        }
    }
}

namespace A.B
{
    public class Foo
    {
        public string Text
        {
            get { return GetType().FullName; }
        }
    }
}

Get familiar with example #1 first.

using B;

namespace A.C
{
    public static class Program
    {
        public static void Main()
        {
            Console.WriteLine(new Foo().Text);
        }
    }
}

Now consider example #2.

namespace A.C
{
    using B; // Notice the placement here.

    public static class Program
    {
        public static void Main()
        {
            Console.WriteLine(new Foo().Text);
        }
    }
}

There is nothing terribly peculiar about example #1. However, things get interesting with example #2. It is imperative that you pay close attention to all identifiers used in the examples. As a fun exercise try to guess what happens without plugging this into the compiler. I will not reveal the answer here because 1) it is easy enough to try yourself and 2) I do not want to ruin the fun.

Will the program:

The question...Where in the C# specification is this behavior described?

I did take a look at section 3.7 in the C# 4.0 specification and especially bullet #2, but I do not think that explains the behavior. If anything it almost makes me think the compiler is behaving contradictory to the specification.

Upvotes: 6

Views: 857

Answers (4)

Eric Lippert
Eric Lippert

Reputation: 660533

The other answers are correct, but one additional note. It helps to remember that

namespace A.C 
{
    using B;

actually is just a short way of writing

namespace A
{
    namespace C
    {
        using B;

Which might make it a bit more clear what is going on here. When resolving B we check A for B before we check the container of A.

If you're interested in ways that namespace lookups can go horribly wrong, see my series of articles about that:

Link

Upvotes: 4

Randolpho
Randolpho

Reputation: 56448

The first example prints "B.Foo", the second example prints "A.B.Foo". This is because in the second example the using B; directive is enclosed inside the A.C namespace.

Why does it use A.B rather than B?

Because namespace lookups follow the same rules as type name qualification lookups. Section 3.8 of the C# spec.

Basically, when the using directive is processed by the compiler , the symbol B is looked for in the A.C namespace. Not finding it, it's looked for in the A namespace. Because it's found there as a sub-namespace of A, it selects that namespace and doesn't go to the global namespace to find the B namespace.

Edit:
As @P.Brian.Mackey suggests, you can get to the B namespace with using global::B;.

Upvotes: 7

LBushkin
LBushkin

Reputation: 131796

I believe the pertinent parts of the spec are:

3.4.1 Namespace members

Namespaces and types that have no enclosing namespace are members of the global namespace. This corresponds directly to the names declared in the global declaration space.

Namespaces and types declared within a namespace are members of that namespace. This corresponds directly to the names declared in the declaration space of the namespace.

Namespaces have no access restrictions. It is not possible to declare private, protected, or internal namespaces, and namespace names are always publicly accessible.

As well as section 9.4.2 which discusses how using directives affect the scoped resoultion of identifiers.

Upvotes: 0

P.Brian.Mackey
P.Brian.Mackey

Reputation: 44295

I didnt read the C# specifications, but I can tell you what's happening simply by deduction. When you put using B inside of the A.C namespace you are no longer in global scope, you are in the scope of the surrounding namespace. First the app will try to resolve in A.C, then in A.

The easiest fix is simply to change the inside using statement to:

using global::B;

But, you can further see this occuring by adding

namespace A.C.B
{
    public class Foo
    {
        public string Text
        {
            get { return GetType().FullName; }
        }
    }
}

Note that you now resolve to A.C.B

Upvotes: 5

Related Questions