3.10 Namensräume (Namespaces)
Die .NET-Klassenbibliothek enthält zahlreiche Klassendefinitionen, die dem Entwickler im Bedarfsfall ihre individuellen Dienste über Methoden bereitstellen. Es kann davon ausgegangen werden, dass sich das Angebot im Laufe der Zeit durch neue Technologien noch deutlich erweitern wird. Dabei sind die benutzerdefinierten Klassen noch nicht berücksichtigt.
Gäbe es für dieses große Angebot keine besondere Verwaltungsstruktur, wäre das Chaos perfekt. Erfahrene Entwickler wissen, wie schwierig es ist, aus den ca. 5.000 bis 6.000 verschiedenen Betriebssystemfunktionen eine bestimmte zu finden. Da hilft auch kein von Microsoft sorgfältig gewählter, beschreibender Funktionsname weiter: Die Suche gleicht dem Stöbern nach der berühmten Stecknadel im Heuhaufen. Dieser Problematik waren sich die .NET-Architekten bewusst und haben das Konzept der Namespaces (Namensräume) eingeführt. Namespaces sind hierarchische, logische Organisationsstrukturen. Sie kategorisieren Typdefinitionen, um das Auffinden einer bestimmten Funktionalität auf ein Minimum an Aufwand zu reduzieren und Mehrdeutigkeiten zu vermeiden.
Namespaces lassen sich sehr gut mit der Ordnerstruktur eines Dateisystems vergleichen. Dabei ähnelt ein Namespace einem Verzeichnis. Jedes Verzeichnis enthält Dateien, die meist logisch miteinander in Beziehung stehen: Beispielsweise können die Dateien eine Anwendung bilden, oder es handelt sich um gemeinsam verwaltete Benutzerdokumente. Innerhalb eines Namespaces werden ebenfalls logisch zusammenhängende Typen verwaltet. Beim Vergleich mit dem physikalischen Dateisystem entspricht eine Typdefintion einer Datei. Innerhalb eines Ordners muss der Name einer Datei eindeutig sein – innerhalb eines Namespaces gilt dasselbe für die Typbezeichner. Im Dateisystem können Verzeichnisse Unterverzeichnisse enthalten, um eine feinere Gliederung zu erzielen. Aus denselben Gründen können Namespaces weitere Namespaces einbetten.
Ein Namespace ist ein Verwaltungskonstrukt, in dem ein oder mehrere Typen logisch gruppiert werden, die funktional in einer verwandtschaftlichen Beziehung stehen. Beispielsweise sind alle Klassen des .NET-Frameworks, die Dateioperationen zur Verfügung stellen, dem Namespace System.IO zugeordnet. Der größte Namespace ist der mit der Bezeichnung System. Er enthält die wichtigsten .NET-Typen und hat aus organisatorischen Gründen weitere, untergeordnete Namespaces.
Zwischen einem Namespace und einer Bibliotheksdatei (DLL), die Typdefinitionen enthält, besteht keine 1:1-Beziehung. Vielmehr kann sich ein Namespace über mehrere DLLs erstrecken. Umgekehrt können in einer DLL-Datei auch mehrere Namespaces definiert werden.
Grundsätzlich ist jede Typdefinition Mitglied eines Namespaces. Folgerichtig wird auch jedweder Programmcode in Namespaces verwaltet. Jedes neue Projekt eröffnet dazu einen neuen Namespace, in dem alle Typen des aktuellen Projekts verwaltet werden.
3.10.1 Zugriff auf Namespaces
Es ist ein Irrtum zu glauben, man könne ohne weitere Maßnahme auf jeden beliebigen Namespace und eine darin verwaltete Klasse Zugriff erhalten. Vielmehr muss dem Projekt die Klassenbibliothek, die den gewünschten Namespace enthält, bekannt gegeben werden.
Damit jedes Projekt von Anfang an eine gewisse Grundfunktionalität hat, werden die wichtigsten Bibliotheken von Anfang an in jedes Projekt eingebunden. Sie finden die Liste der entsprechenden Dateiverweise im Projektmappen-Explorer, wenn Sie den Knoten Verweise öffnen. Die Dateiendung wird in der Verweisliste nicht mit angegeben (siehe Abbildung 3.6).
Abbildung 3.6 Der geöffnete Knoten »Verweise«
Damit stehen dem Entwickler bereits nach dem Anlegen eines neuen Projekts sehr viele Klassen zur Verfügung – nämlich die, die in den Bibliotheken enthalten sind, auf die verwiesen wird. Sollte es sich im Laufe der Entwicklungszeit herausstellen, dass darüber hinaus noch weitere benötigt werden, muss die Verweisliste mit den entsprechenden Bibliotheken ergänzt werden. Dazu öffenen Sie das Kontextmenü des Knotens Verweise im Projektmappen-Explorer und wählen Verweis hinzufügen.... Daraufhin wird das in Abbildung 3.7 dargestellte Dialogfenster Verweis-Manager angezeigt. In der Registerkarte .NET wird die gewünschte Datei markiert und über die Schaltfläche OK zur Liste der ausgewählten Komponenten hinzugefügt. In Tabelle 3.3 sind die Registerkarten des Dialogs erläutert.
Abbildung 3.7 Der Dialog zum Hinzufügen von Verweisen
Registerkarte | Beschreibung |
Assemblies |
Wenn diese Registerkarte aktiviert wird, werden weitere Untergruppen angezeigt: Framework, Erweiterungen und unter Umständen auch noch Aktuell. Unter Framework sind alle Bibliotheken zu finden, die das .NET Framework in der Ausgangslage zur Verfügung stellt. Unter Erweiterungen sind die Komponenten externer Anbieter zu finden, und in Aktuell findet man alle Verweise, die abweichend von der Projektvorlage hinzugefügt worden sind. |
COM |
Möchten Sie eine Komponente nutzen, die für COM/ActiveX entwickelt worden ist, suchen Sie die gewünschte Komponente hier. |
Durchsuchen |
Über diese Lasche können Sie zu einer Komponente im Dateisystem navigieren. |
Projektmappe |
Hier werden alle (kompatiblen) Projekte in der Projektmappe aufgelistet. |
Wenn Sie wissen, welche Klasse Sie in Ihrem Projekt benötigen, stellt sich nur noch die Frage, in welcher Datei die Klasse zu finden ist. Die Lösung ist sehr einfach, wenn Sie sich das Datenblatt der entsprechenden Klasse in der .NET-Dokumentation ansehen. Darin werden Sie sowohl die Angabe des Namespaces finden, dem die Klasse zugeordnet ist, als auch die Angabe der zugehörigen Bibliotheksdatei.
3.10.2 Die »using«-Direktive
Standardmäßig muss beim Zugriff auf eine Klasse auch der Namespace angeführt werden, dem die Klasse zugeordnet ist. Betrachten wir dazu das schon häufig benutzte Beispiel der Methode WriteLine der Klasse Console, die zum Namespace System gehört. Um im Konsolenfenster eine Ausgabe zu erhalten, müsste streng genommen
System.Console.WriteLine("Hallo Welt");
codiert werden. Eine Angabe, die aus Namespace und Klassenname besteht, wird als vollqualifizierter Name bezeichnet und ähnelt einer kompletten Pfadangabe im physikalischen Dateisystem. Vollqualifizierte Namen führen oft zu sehr langen, unübersichtlichen und schlecht lesbaren Ausdrücken im Programmcode, insbesondere wenn mehrere Namespaces ineinander verschachtelt sind. C# bietet uns mit der using-Direktive Abhilfe. Mit
using System;
kann an späterer Stelle im Programmcode auf alle Typen des so bekannt gegebenen Namespaces unter Angabe des Typbezeichners zugegriffen werden, ohne den vollqualifizierten Namen angeben zu müssen:
Console.WriteLine("Hallo Welt");
using-Direktiven stehen außerhalb der Klassendefinitionen und beziehen sich nur auf die Quellcodedateien, in denen sie angegeben sind.
3.10.3 Globaler Namespace
In .NET gibt es einen sogenannten globalen Namespace. Diesem werden die folgenden Elemente zugeordnet:
- alle Top-Level-Namespaces
- alle Typen, die keinem Namespace zugeordnet sind
Der Zugriff auf den globalen Namespace unterliegt einer speziellen Syntax und wird in Abschnitt 3.10.5 erläutert.
3.10.4 Vermeiden von Mehrdeutigkeiten
Namespaces dienen zur Strukturierung und Gruppierung von Klassen mit ähnlichen Merkmalen, aber auch zur Vermeidung von Mehrdeutigkeiten. Konflikte aufgrund gleicher Typbezeichner werden durch Namespaces vermieden. Allerdings kann die Bekanntgabe mehrerer Namespaces mit using Probleme bereiten, sollten in zwei verschiedenen Namespaces jeweils gleichnamige Typen existieren. Dann hilft using auch nicht weiter. Angenommen, in den beiden fiktiven Namespaces MyApplication und YourApplication wäre jeweils eine Klasse Person definiert, dann würde der folgende Code wegen der Uneindeutigkeit des Klassenbezeichners einen Fehler verursachen:
using MyApplication;
using YourApplication;
class Demo {
static void Main(string[] arr) {
Person obj = new Person();
[...]
}
}
Die Problematik lässt sich vermeiden, wenn der Namespace der Klasse Person näher spezifiziert wird, beispielsweise mit:
MyApplication.Person person = new MyApplication.Person();
Es gibt auch noch eine weitere Möglichkeit, um den Eindeutigkeitskonflikt oder eine überlange Namespace-Angabe zu vermeiden: die Definition eines Alias. Während die einfache Angabe ohne Alias hinter using nur einen Namespace erlaubt, ersetzt ein Alias den vollständig qualifizierenden Typbezeichner. Damit könnte die Klasse Person in den beiden Namespaces auch wie folgt genutzt werden:
using FirstPerson = MyApplication.Person;
using SecondPerson = YourApplication.Person;
[...]
FirstPerson person = new FirstPerson();
Genauso können Sie, falls Sie Spaß daran haben, die Klasse Console »umbenennen«, z. B. in Ausgabe:
using Ausgabe = System.Console;
[...]
Ausgabe.WriteLine("Hallo Welt");
3.10.5 Namespaces festlegen
Jedem neuen C#-Projekt wird von der Entwicklungsumgebung automatisch ein Namespace zugeordnet. Standardmäßig sind Namespace- und Projektbezeichner identisch.
Solange sich Typen innerhalb desselben Namespaces befinden, können sie sich gegenseitig direkt mit ihrem Namen ansprechen. Die Klassen DemoA, DemoB und DemoC des folgenden Codefragments sind demselben Namespace zugeordnet und benötigen deshalb keine vollqualifizierte Namensangabe.
namespace MyApplication
{
class DemoA {[...]}
class DemoB {{...]}
class DemoC {[...]}
}
Jeden Namespace können Sie selbstverständlich nach eigenem Ermessen benennen. Häufig verwenden die Unternehmen dazu ihren Unternehmensnamen. Zudem lassen sich auch mehrere Namespaces angeben, wie das folgende Listing zeigt:
using System;
using MyApp;
using ConsoleApplication;
namespace ConsoleApplication
{
class Program {
static void Main(string[] args) {
// erfordert: using MyApp;
Demo obj = new Demo();
}
}
}
namespace MyApp
{
public class Demo {
public void Test() {
// erfordert: using ConsoleApplication;
Program obj = new Program();
}
}
}
Listing 3.50 Angabe mehrerer Namespaces
Das Beispiel zeigt die beiden parallelen Namespaces ConsoleApplication und MyApp. Jeder enthält eine Klasse mit einer Methode, in der ein Objekt vom Typ der Klasse aus dem anderen Namespace instanziiert wird. Da der Zugriff namespace-übergreifend ohne die Angabe des vollqualifizierten Bezeichners erfolgt, müssen beide Namespaces durch using bekannt gegeben werden.
Eingebettete Namespaces
Ein Namespace kann mit einem Ordner des Dateisystems verglichen werden. So wie ein Ordner mehrere Unterordner enthalten kann, können auch Namespaces eine hierarchische Struktur bilden. Der oberste Namespace, der entweder dem Projektnamen entspricht oder manuell verändert worden ist, bildet die Wurzel der Hierarchie, ähnlich einer Laufwerksangabe.
Soll dieser Stamm-Namespace eine feinere Strukturierung aufweisen und eingebettete Namespaces verwalten, wird innerhalb eines Namespaces ein weiterer, untergeordneter Namespace definiert:
namespace Outer {
class DemoA {
static void Main(string[] args) {
DemoB obj = new DemoB();
}
}
namespace Inner {
class DemoB {
public void TestProc() {/*...*/}
}
}
}
Listing 3.51 Verschachtelte Namespaces
Ein Typ in einem übergeordneten Namespace hat nicht automatisch Zugriff auf einen Typ in einem untergeordneten Namespace. Damit das Codefragment auch tatsächlich fehlerfrei kompiliert werden kann, ist es erforderlich, mit
using Outer.Inner;
den inneren Gültigkeitsbereich den Typen in der übergeordneten Ebene bekannt zu geben.
3.10.6 Der »::«-Operator
Auch für Namespaces lässt sich ein Alias festlegen, beispielsweise:
using EA = System.IO;
Sie können nun wie gewohnt den Punktoperator auf den Alias anwenden, also:
EA.StreamReader reader = new EA.StreamReader("...");
Seit dem .NET Framework 2.0 bietet sich aber auch die Möglichkeit, mit dem ::-Operator auf Typen aus Namespace-Aliasen zu verweisen.
EA::StreamReader reader = new EA::StreamReader("...");
Die Einführung des ::-Operators hatte den Grund, unschöne Effekte zu vermeiden, die sich im Zusammenhang mit Namespace-Aliasen und dem Punkt-Operator ergeben können. Sehen Sie dazu den folgenden Beispielcode an:
using System;
using Document = Tollsoft.Developement.Office;
namespace ConsoleApplication {
class Program {
static void Main(string[] args) {
Document.Demo demo = new Document.Demo();
}
}
}
namespace Tollsoft.Developement.Office {
class Demo { }
}
Richten Sie Ihr Augenmerk auf die Anweisung in der Methode Main. Die Syntax Document.Demo lässt nicht eindeutig erkennen, ob es sich bei Document um einen Namespace handelt oder um einen Namespace-Alias. Zudem wäre auch noch denkbar, dass Demo eine innere Klasse von Document ist. Die Verwendung des ::-Operators würde zumindest demjenigen Entwickler eine Hilfe sein, der sich in den Quellcode neu einarbeiten muss. Besser wäre also die folgende Anweisung:
Document::Demo demo = new Document::Demo();
Noch bedeutender wird der ::-Operator, wenn in einer anderen Assembly, auf die im Projekt verwiesen wird, ein Namespace oder ein Typ mit dem gleichen Namen wie der Alias angeboten wird. Die Syntax Document.Demo würde dann sogar zu einem Fehler führen, während Document::Demo eindeutig ist.
Der ::-Operator gestattet auch den Zugriff auf den globalen Namespace. Das setzt nur die Voranstellung des C#-Schlüsselworts global voraus. Auf Typen, die Sie nicht explizit einem Namespace zugeordnet haben, können Sie auf diese Weise zugreifen (siehe Abbildung 3.8).
Man sollte diese Überlegung nicht einfach mit der Hand vom Tisch wischen, wenn aktuell kein Konflikt mit einem Namespace oder Typ in einer anderen Assembly vorliegt. Möglicherweise wird die Assembly, auf die verwiesen wird, später in einer neueren Version ausgeliefert. Spätestens dann könnte es zu einem Eindeutigkeitskonflikt kommen.
Abbildung 3.8 Der globale Namespace
Halten wir an dieser Stelle den Einsatz des ::-Operators fest:
- Der ::-Operator ist notwendig, um mit Hilfe von global auf den globalen Namespace zuzugreifen.
- Der ::-Operator sollte benutzt werden, um bei Verwendung eines Namespace-Alias kenntlich Eindeutigkeitskonflikte zu vermeiden.
3.10.7 Unterstützung von Visual Studio 2012 bei den Namespaces
Mit dem Anlegen eines neuen Projekts gibt Visual Studio eine Reihe von Namespaces mit using an. Welche das sind, hängt von der Projektvorlage ab. Dabei sind Namespaces, die durchaus benötigt werden, aber auch solche, von denen angenommen wird, dass ein Entwickler sie vielleicht gebrauchen könnte.
Sie können die in einer Quellcodedatei nicht benötigten Namespaces mit Hilfe der Entwicklungsumgebung sehr einfach loswerden. Öffnen Sie dazu das Kontextmenü des Code-Editors, und wählen Sie hier Using-Direktiven organisieren. Anschließend können Sie entweder die nicht benötigten Direktiven löschen oder alle sortieren lassen (siehe Abbildung 3.9).
Ein ebenfalls sehr sinnvolles Feature ist das automatische Hinzufügen von benötigten using-Direktiven. Das setzt allerdings voraus, dass Sie die Klasse und natürlich auch deren Schreibweise hinsichtlich der Groß- und Kleinschreibung kennen. Geben Sie einfach den Klassenbezeichner im Editor ein, z. B. StreamReader. Anschließend setzen Sie den Mauscursor auf die Typangabe, öffnen das Kontextmenü und wählen hier auflösen. Sie können sich dann entscheiden, ob die entsprechende using-Direktive in den Kopf der Quellcodedatei geschrieben werden soll oder der Typ vollqualifizierend im Code angegeben wird (siehe Abbildung 3.10).
Abbildung 3.9 Verwalten der »using«-Direktiven in Visual Studio 2012
Abbildung 3.10 Unterstützung bei der Angabe der erforderlichen »using«-Direktiven
Ihre Meinung
Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de.