| 

.NET C# Java Javascript Exception

3
Beim Lesen der letzten dotnetpro habe ich mich wieder an eine kleine, nennen wir es: Merkwürdigkeit in .NET erinnert, über die ich mich vor Jahren bei meinen ersten Versuchen mit C# mächtig gewundert habe. Seitdem weiß ich, daß es so ist, aber eine Erklärung, wozu das gut sein soll, fehlt mir bis heute. Vielleicht weiß einer von Euch Rat?

Gegeben sei eine Klasse X in einem Namespace A:

namespace A
{
public class X
{
}
}

und eine Klasse Y in einem Namespace B.A

namespace B.A
{
public class Y
{
}
}

Der Haken ist nun: ich kann in Y nur dann auf X zugreifen, wenn ich den Namespace A mit dem Präfix "global::" qualifiziere. Weder ein einfaches "A.X" noch "using A;" funktionieren, weil der Compiler offenbar "A" ergänzt zu "B.A", bevor er überhaupt anfängt, nach X zu suchen.

Meine Frage nun: warum ist das so? Gibt es irgendeinen Nutzen für dieses Vorgehen? Ich war damals davon ausgegangen, dass der Namespace-Name immer komplett angegeben werden muss, entweder in einer using-Direktive oder direkt bei der Verwendung, und halte auch heute noch dieses "Mitdenkenwollen" des Compilers für hinderlich und nicht erforderlich.

Dazu noch drei Randnotizen:

a) Kurioserweise weiß der Visual-Studio-Editor, was ich will: wenn ich innerhalb von Y "X" eintippe, bekomme ich ein SmartTag, mit dem ich "using A.X" oder "A.X" ergänzen kann - was dann aber wie gesagt nicht funktioniert.

b) Auf die Nase gefallen bin ich damals, weil ich die (schlechte) Idee hatte, die Unit-Tests für die Klassen aus A im Namespace Test.A unterzubringen. Das war vor allem auch deshalb besonders schmerzhaft, weil wir den von den MSTest-Helferlein generierten Code zum Zugriff auf private Member nutzen wollten, der dann in einigen Fällen nicht kompilierte, weil der "global::"-Qualifier nicht mit generiert wurde.

c) Lesson learned: ein Namespace-Name sollte nie rechtsseitiger Teil eines anderen Namespace-Namens sein.

Update: Mir ist erst heute aufgefallen, dass in der Fragestellung ein Fehler steckt. Ein "using A;" außerhalb von "namespace B.A" funktioniert. Mir war das nicht aufgefallen, weil ich die Code-Beispiele "der Einfachheit halber" in eine Datei geschrieben hatte und dazu auch das using-Statement in den Namespace mit hineingenommen habe, in der falschen Annahme, dass es dann der Situation in zwei getrennten Dateien entspricht. Am Grundproblem ändert das aber nichts: warum, d.h. zu welchem Zweck, wird ein "A.*" interpretiert als Abkürzung für "B.A.*"?
16.03.2011
Matthias Hlawatsch 13,2k 4 9
4 Antworten
3
Ich nehme meine Antwort komplett zurück, da Golo mir bewiesen hat, dass sie falsch ist. Sorry...
16.03.2011
Hendrik Lösch 1,5k 1 9
3
Das Problem kann man sehr wohl haben. Nenn zB ein Projekt mal Firmenname.Softwarezeugs.UI.Console, analog zu Firmenname.Softwarezeugs.UI.Web, dann hast Du darin Probleme, mit einem using System; auf Console.WriteLine zuzugreifen ...

Da hilft dann nur das vollqualifizierte global::System.Console.WriteLine() ...
Golo Roden 16.03.2011
@Golo Danke für das Beispiel - ich hatte noch überlegt, ob ich die Kollision zwischen Klassen- und Namespace-Name noch mit reinnehme, war aber nicht mehr draufgekommen, wo das sichtbar wird. Aber kannst Du erklären, warum es mit einem "using System;" außerhalb der namespace-Deklaration nicht funktioniert (sprich global:: ist notwendig), mit einem "using System;" innerhalb hingegen schon? Hat das was mit den Scopes zu tun, die devio angesprochen hat? Läßt sich daraus vielleicht sogar eine Best Practice ableiten, wo die usings stehen sollten?
Matthias Hlawatsch 17.03.2011
Die Stylecop Rule SA1200 empfiehlt Usings grundsätzlich in die Namespaces zu schreiben. Auf diese Weise verdecken importierte Typen diejenigen mit gleichem Namen aus den "eigenen" Namespaces. Ob das gut oder schlecht ist, bin ich mir nicht so sicher...
Hendrik Lösch 17.03.2011
Trotzdem +1 - wegen dem Hinweis auf die Stylecop-Rule und weil wir sonst Golos Beispiel möglicherweise nicht erfahren hätten ;-)
Matthias Hlawatsch 13.04.2011
@ Matthias - sorry, habe Deinen Kommentar erst jetzt gesehen. Hast Du dafür mal ein Beispiel?
Golo Roden 13.04.2011
@Golo: Du hattest doch sowas gemeint:
namespace Me.Task.Console
{
class C
{
public void Do()
{
Console.WriteLine("Hello");
}
}
}
oder?
Ein "using System;" vor der Namespace-Deklaration funktioinert nicht, danach hingegen schon. Und System.Console.WriteLine() geht auch, ohne "global::".
Matthias Hlawatsch 13.04.2011
2
Das ganze funktioniert wie ein Baum.
Namespace A ist dabei ein Hauptast.
Namespace B ist ein weiterer Hauptast.

Der Stamm hat den Namespace global.

Somit kann man von Ast A zum Ast B nur über global.
Und der Namespace A aus dem Namespace B ( = B.A) hat nichts mit dem Namespace A zu tun.
16.03.2011
endlos 21 1
Das Bild mit dem gemeinsamen Stamm "global" ist hilfreich. Unlogisch finde ich aber, warum ich aus Y in B.A heraus X nur auf dem Weg über global sehen soll. Erstens würde es für ein Z in C auch ohne global gehen, zweitens ist es beinah schon böswillig, ein "A." in Y als Hinweis auf B.A zu interpretieren, denn alle Typen aus B.A sehe ich in Y auch ohne Namespace-Angabe - warum sollte ich sie dann extra hinschreiben. Es ist doch also "klar", dass ich was anderes meine.
Matthias Hlawatsch 13.04.2011
2
Warum das so ist, klärt Section 3.7 Scopes (unter C:\Program Files\Microsoft Visual Studio 10.0\VC#\Specifications\1033\CSharp Language Specification.doc zu finden):

Scopes can be nested, and an inner scope may redeclare the meaning of a name from an outer scope [...]. The name from the outer scope is then said to be hidden in the region of program text covered by the inner scope, and access to the outer name is only possible by qualifying the name.


Um sich bei der Namespace-Adressierung das global:: zu ersparen, kann man innerhalb des Namespace einen Namespace-Alias definieren:

namespace A
{
public class X
{
}
}
namespace B.A
{
using AA = global::A;

public class Y
{
A.Y y1; // B.A.Y
AA.X x1; // A.X
}
}
16.03.2011
devio 302 3
Ich finde, die Spec klärt, dass es so ist, aber nicht warum (sprich warum hat man das so spezifiziert).
Matthias Hlawatsch 13.04.2011
+1 Trotzdem danke für das Zitat, hat mir geholfen, dem Verstehen näher zu kommen.
Matthias Hlawatsch 13.04.2011
0
Sorry für das späte Feedback. Ich habe das Thema leider eine Weile liegenlassen müssen und wollte auch selber nochmal ein wenig herumprobieren. Danke für alle Antworten und Kommentare!

Bitte beachtet das Update der Frage, da war ein Fehler drin - sorry!

Eine echte Antwort im Sinne von "wofür ist das gut?" fehlt mir leider immer noch. Am wahrscheinlichsten erscheint mir, dass da jemand gemeint hat, eine abgekürzte Schreibweise für Namespaces wäre eine praktische Sache, d.h. wenn zwei Namespaces N1 und N2 im selben Parentnamespace liegen, kann ich eine Klasse K aus N1 in N2 mit N1.K refererenzieren. Nun, wenn das wirklich der Grund war, ist meine Meinung: viel Aufwand für wenig Nutzen.

Ausnahmsweise (als großer C#-Fan) finde ich, dass dieses Thema in Java besser gelöst ist: zum einen schließen schon mal die Namenskonventionen Verwechslungen zwischen Klassen und Packages aus, zum andern habe ich nur die Wahl zwischen der vollständigen Qualifizierung des Klassennamens mit dem Package-Namen und einem import-Befehl, wieder mit vollständigem Paketnamen. das ist für mich deutlich einfacher, leicht zu lesen, dank guter Unterstützung in Eclipse auch problemlos zu bedienen (automatisches Ergänzen von imports, beim Kopieren von Code werden die imports mitgezogen - hallo Visual-Studio-Team?), und ich habe noch kein Szenario gefunden, das damit nicht abgebildet werden kann.

Gebracht hat mir die Diskussion vor allem Einblicke in die Unterschiede von usings innerhalb und außerhalb von namespace-Deklarationen. Dazu habe ich auch noch einen sehr lesenwerten Beitrag bei stackoverflow gefunden. Eigentlich ein Grund mehr, die Stylecop-Regel zu befolgen und die usings nach innen zu nehmen - wenn nicht die VS-Templates und -Snippets gerade das Gegenteil machen würden (mal abgesehen davon, dass mein konstruiertes Beispiel auch nur mit einem using außerhalb des Namespaces funktioniert).

Ansonsten bleibt wohl nur: immer im Hinterkopf behalten, dass der C#-Compiler beim Auflösen von Bezeichnern Namespaces und Klassennamen ziemlich gleichwertig behandelt und bei den Namespaces von innen/unten nach außen/oben "denkt".
13.04.2011
Matthias Hlawatsch 13,2k 4 9

Stelle deine .net-Frage jetzt!
TOP TECHNOLOGIES CONSULTING GmbH