6 Stimmen

Können Sie diesen Grenzfall erklären, der den C# 'using'-Schlüssel mit Namespace-Deklarationen und Elementen betrifft?

Betrachten Sie den folgenden kurzen Codeschnipsel.

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; }
        }
    }
}

Machen Sie sich mit Beispiel #1 vertraut.

using B;

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

Betrachten Sie nun Beispiel #2.

namespace A.C
{
    using B; // Beachten Sie die Platzierung hier.

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

An Beispiel #1 ist nichts besonders auffälliges. Allerdings wird es interessant mit Beispiel #2. Es ist wichtig, dass Sie alle Bezeichner in den Beispielen genau beachten. Als lustige Übung versuchen Sie zu erraten, was passiert, ohne dies in den Compiler einzugeben. Ich werde hier die Antwort nicht verraten, weil 1) es einfach genug ist, es selbst auszuprobieren und 2) ich den Spaß nicht verderben möchte.

Wird das Programm:

  • nicht kompilieren
  • B.Foo anzeigen
  • A.B.Foo anzeigen

Die Frage...Wo im C#-Spezifikation wird dieses Verhalten beschrieben?

Ich habe mir Abschnitt 3.7 in der C# 4.0-Spezifikation angesehen, insbesondere Punkt #2, aber ich glaube nicht, dass dies das Verhalten erklärt. Wenn überhaupt, lässt es mich fast denken, dass der Compiler sich widersprüchlich zur Spezifikation verhält.

0 Stimmen

Sie sollten diese Frage an den Autor dieses Blogs posten: blogs.msdn.com/b/ericlippert Er wird definitiv eine Antwort für Sie haben.

0 Stimmen

Ich kenne seinen Blog. Er postet häufig auf SO, also könnte er hier einen Versuch starten.

0 Stimmen

Ja, auch ich warte auf die Antwort von Eric :)

7voto

Randolpho Punkte 54011

Das erste Beispiel gibt "B.Foo" aus, das zweite Beispiel gibt "A.B.Foo" aus. Dies liegt daran, dass im zweiten Beispiel die using B; Anweisung im A.C Namespace eingeschlossen ist.

Warum wird A.B anstelle von B verwendet?

Weil Namespace-Suchen den gleichen Regeln wie Typnamenqualifikationen folgen. Abschnitt 3.8 der C# Spezifikation.

Grundsätzlich, wenn die using Anweisung vom Compiler verarbeitet wird, wird das Symbol B im A.C Namespace gesucht. Wenn es dort nicht gefunden wird, wird es im A Namespace gesucht. Da es dort als Unter-Namespace von A gefunden wird, wählt es diesen Namespace aus und durchsucht nicht den globalen Namespace nach dem B Namespace.

Bearbeiten:
Wie @P.Brian.Mackey vorschlägt, kann man mit using global::B; zum B Namespace gelangen.

1 Stimmen

Ja, ich denke du hast recht. Das relevante Stück ist im 3.8 Punkt #1 Unterpunkt #2. Es beschreibt definitiv, wenn auch nicht so klar, dieses Backtracking-Verhalten. Guter Fang.

0 Stimmen

@ Gehirn: Das ist der Eine, zusammen mit den drei Punkten danach.

5voto

P.Brian.Mackey Punkte 41258

Ich habe die C# Spezifikationen nicht gelesen, aber ich kann Ihnen einfach durch Deduktion sagen, was passiert. Wenn Sie das using B innerhalb des A.C-Namespace platzieren, befinden Sie sich nicht mehr im globalen Bereich, sondern im Bereich des umgebenden Namespace. Zuerst versucht die App, in A.C aufzulösen, dann in A.

Die einfachste Lösung besteht darin, die innere using-Anweisung einfach zu ändern:

using global::B;

Aber Sie können sehen, dass dies weiterhin geschieht, indem Sie hinzufügen

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

Beachten Sie, dass Sie nun zu A.C.B auflösen.

0 Stimmen

+1, aber ich denke, du hast die Reihenfolge, in der die Auflösung erfolgt, umgekehrt. Es sollte zuerst versuchen, in A.C aufzulösen, dann in A.

4voto

Eric Lippert Punkte 628543

Die anderen Antworten sind richtig, aber hier noch eine zusätzliche Anmerkung. Es hilft, sich daran zu erinnern, dass

namespace A.C 
{
    using B;

eigentlich nur eine verkürzte Schreibweise ist für

namespace A
{
    namespace C
    {
        using B;

Was es vielleicht etwas klarer macht, was hier passiert. Bei der Auflösung von B überprüfen wir A auf B, bevor wir den Container von A überprüfen.

Wenn Sie daran interessiert sind, wie Namespace-Suchvorgänge schief gehen können, sehen Sie meine Artikelserie dazu:

Link

0voto

LBushkin Punkte 124894

Ich glaube, die relevanten Teile der Spezifikation sind:

3.4.1 Namespace-Mitglieder

Namespaces und Typen, die keinen umschließenden Namespace haben, sind Mitglieder des globalen Namespace. Dies entspricht direkt den Namen, die im globalen Deklarationsraum deklariert sind.

Namespaces und Typen, die innerhalb eines Namespace deklariert sind, sind Mitglieder dieses Namespace. Dies entspricht direkt den im Deklarationsraum des Namespace deklarierten Namen.

Namespaces haben keine Zugriffsbeschränkungen. Es ist nicht möglich, private, geschützte oder interne Namespaces zu deklarieren, und Namespace-Namen sind immer öffentlich zugänglich.

Sowie Abschnitt 9.4.2, der erörtert, wie mit Hilfe von Direktiven die scopebasierte Auflösung von Bezeichnern beeinflusst wird.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X