Warum trennen .NET-Module Moduldateinamen von Namespaces?

9

In Implementierungen der Programmiersprache Scheme (R6RS-Standard) kann ich ein Modul wie folgt importieren:

(import (abc def xyz))

Das System versucht, nach einer Datei zu suchen, $DIR/abc/def/xyz.slsin der $DIRsich ein Verzeichnis befindet, in dem Sie Ihre Scheme-Module aufbewahren. xyz.slsist der Quellcode für das Modul und wird bei Bedarf im laufenden Betrieb kompiliert.

Die Ruby-, Python- und Perl-Modulsysteme sind in dieser Hinsicht ähnlich.

C # ist dagegen etwas komplizierter.

Erstens haben Sie DLL-Dateien, auf die Sie pro Projekt verweisen müssen. Sie müssen jeden explizit referenzieren. Dies ist mehr als nur das Ablegen von DLL-Dateien in einem Verzeichnis und das Aufnehmen durch C # nach Namen.

Zweitens gibt es keine Eins-zu-Eins-Namenskorrespondenz zwischen dem DLL-Dateinamen und den von der DLL angebotenen Namespaces. Ich kann diese Flexibilität schätzen, aber sie kann auch außer Kontrolle geraten (und hat).

Um dies konkret zu machen, wäre es schön, wenn using abc.def.xyz;C # , wenn ich das sage , versuchen würde, eine Datei abc/def/xyz.dllin einem Verzeichnis zu finden , in dem C # suchen kann (konfigurierbar pro Projekt).

Ich finde die Ruby-, Python-, Perl- und Schema-Art des Umgangs mit Modulen eleganter. Es scheint, dass aufstrebende Sprachen eher zum einfacheren Design passen.

Warum macht die .NET / C # -Welt die Dinge auf diese Weise mit einer zusätzlichen Indirektionsebene?

Dharmatech
quelle
10
Der Assembler- und Klassenauflösungsmechanismus von .NET funktioniert seit über 10 Jahren einwandfrei. Ich denke, Ihnen fehlt ein grundlegendes Missverständnis (oder nicht genügend Forschung) darüber, warum es so konzipiert ist - z. B. um die Umleitung von Baugruppen usw.
Kev
Ich bin mir ziemlich sicher, dass die DLL-Auflösung einer using-Anweisung die Side-by-Side-Ausführung unterbrechen würde. Auch wenn es eine 1 zu 1 gäbe, würden Sie 50 DLLs für alle Namespaces in mscorlib benötigen, oder sie müssten die Idee von Namespaces fallen lassen
Conrad Frix
Eine zusätzliche Indirektionsebene? Hmmmmm .... dmst.aueb.gr/dds/pubs/inbook/beautiful_code/html/Spi07g.html
user541686
@ Gilles Danke für die Bearbeitung und Verbesserung der Frage!
Dharmatech
Einige Ihrer Punkte sind Visual Studio eigen, und der Vergleich mit einer Sprache ist nicht ganz fair ( z. B. DLL-Referenzen in Projekten). Ein besserer Vergleich für die vollständige .NET-Umgebung wäre Java + Eclipse.
Ross Patterson

Antworten:

22

Die folgende Anmerkung in den Framework Design Guidelines, Abschnitt 3.3. Namen von Assemblys und DLLs bieten Einblicke in die Gründe, warum Namespaces und Assemblys getrennt sind.

BRAD ABRAMS Zu Beginn des Entwurfs der CLR haben wir beschlossen, die Entwickleransicht der Plattform (Namespaces) von der Verpackungs- und Bereitstellungsansicht der Plattform (Assemblys) zu trennen. Diese Trennung ermöglicht es, jede unabhängig nach ihren eigenen Kriterien zu optimieren. Es steht uns beispielsweise frei, Namespaces für Gruppentypen zu faktorisieren, die funktional miteinander verbunden sind (z. B. alle E / A-Elemente in System.IO), während die Assemblys aus Gründen der Leistung (Ladezeit), Bereitstellung, Wartung oder Versionierung berücksichtigt werden können .

Conrad Frix
quelle
Scheint die bisher maßgeblichste Quelle zu sein. Danke Conrad!
Dharmatech
+1 für die verdammt maßgebliche Antwort. Aber auch jede Frage " Warum macht C # <X>? " Muss durch die Linse von " Java macht <X> wie folgt: " betrachtet werden, da C # (explizit oder nicht) eine Reaktion auf Suns und war Die unterschiedlichen Agenden von Microsoft für Java unter Windows.
Ross Patterson
6

Es erhöht die Flexibilität und ermöglicht das Laden der Bibliotheken (was Sie in Ihrer Frage als Module bezeichnen) bei Bedarf.

Ein Namespace, mehrere Bibliotheken:

Einer der Vorteile ist, dass ich eine Bibliothek leicht durch eine andere ersetzen kann. Angenommen, ich habe einen Namespace MyCompany.MyApplication.DALund eine Bibliothek DAL.MicrosoftSQL.dll, die alle SQL-Abfragen und andere Dinge enthält, die für die Datenbank spezifisch sein können. Wenn die Anwendung mit Oracle kompatibel sein soll, füge ich einfach hinzu DAL.Oracle.dllund behalte den gleichen Namespace bei. Ab sofort kann ich die Anwendung mit einer Bibliothek für Kunden bereitstellen, die die Kompatibilität mit Microsoft SQL Server benötigen, und mit der anderen Bibliothek für Kunden, die Oracle verwenden.

Das Ändern des Namespace auf dieser Ebene würde entweder zu doppeltem Code führen oder dazu, dass alle usings im Quellcode für jede Datenbank geändert werden müssen.

Eine Bibliothek, mehrere Namespaces:

Das Vorhandensein mehrerer Namespaces in einer Bibliothek ist auch im Hinblick auf die Lesbarkeit von Vorteil. Wenn ich in einer Klasse nur einen der Namespaces verwende, setze ich nur diesen oben in die Datei.

  • Alle Namespaces einer großen Bibliothek zu haben, wäre sowohl für die Person, die den Quellcode liest, als auch für die Autorin selbst ziemlich verwirrend, da Intellisense in einem bestimmten Kontext zu viele Dinge vorschlägt.

  • Bei kleineren Bibliotheken hätte eine Bibliothek pro Datei Auswirkungen auf die Leistung: Jede Bibliothek muss bei Bedarf in den Speicher geladen und von der virtuellen Maschine verarbeitet werden, wenn die Anwendung ausgeführt wird. Weniger zu ladende Dateien bedeuten eine etwas bessere Leistung.

Arseni Mourzenko
quelle
Der zweite Fall erfordert nicht, dass Dateinamen von Namespaces getrennt sind (obwohl das Zulassen einer solchen Trennung die Trennung erheblich erleichtert), da eine bestimmte Assembly leicht viele Unterordner und Dateien haben kann. Darüber hinaus ist es auch möglich, mehrere Assemblys in dieselbe DLL einzubetten (z. B. mithilfe von ILMerge ). Java Works verfolgt diesen Ansatz.
Brian
2

Es sieht so aus, als würden Sie die Terminologie von "Namespace" und "Modul" überladen. Es sollte keine Überraschung sein, dass Sie Dinge als "indirekt" betrachten, wenn sie nicht Ihren Definitionen entsprechen.

In den meisten Sprachen, die Namespaces unterstützen, einschließlich C #, ist ein Namespace kein Modul. Ein Namespace ist eine Möglichkeit, Namen zu erfassen. Module sind eine Möglichkeit, das Verhalten zu bestimmen.

Während die .Net-Laufzeit die Idee eines Moduls unterstützt (mit einer etwas anderen Definition als der, die Sie implizit verwenden), wird sie im Allgemeinen eher selten verwendet. Ich habe es nur in Projekten gesehen, die in SharpDevelop erstellt wurden, hauptsächlich, damit Sie eine einzelne DLL aus Modulen erstellen können, die in verschiedenen Sprachen erstellt wurden. Stattdessen erstellen wir Bibliotheken mithilfe einer dynamisch verknüpften Bibliothek.

In C # werden Namespaces ohne "Indirektionsebene" aufgelöst, solange sie sich alle in derselben Binärdatei befinden. Jede erforderliche Indirektion liegt in der Verantwortung des Compilers und Linkers, über die Sie nicht viel nachdenken müssen. Sobald Sie mit dem Erstellen eines Projekts mit mehreren Abhängigkeiten beginnen, verweisen Sie auf externe Bibliotheken. Sobald Ihr Projekt auf eine externe Bibliothek (DLL) verwiesen hat, findet der Compiler diese für Sie.

Wenn Sie im Schema eine externe Bibliothek laden müssen, müssen Sie zunächst etwas tun (#%require (lib "mylib.ss"))oder die Fremdfunktionsschnittstelle direkt verwenden, wie ich mich erinnere. Wenn Sie externe Binärdateien verwenden, haben Sie den gleichen Arbeitsaufwand, um externe Binärdateien aufzulösen. Möglicherweise haben Sie meistens Bibliotheken verwendet, die so häufig verwendet werden, dass es einen schemabasierten Shim gibt, der diese von Ihnen abstrahiert. Wenn Sie jedoch jemals eine eigene Integration in eine Bibliothek eines Drittanbieters schreiben müssen, müssen Sie im Wesentlichen einige Arbeiten ausführen, um sie zu laden " die Bibliothek.

In Ruby sind Module, Namespaces und Dateinamen weitaus weniger miteinander verbunden, als Sie vermuten. Das LOAD_PATH macht die Dinge etwas kompliziert, und Moduldeklarationen können überall sein. Python ist wahrscheinlich näher dran, Dinge so zu tun, wie Sie es in Schema sehen, außer dass Bibliotheken von Drittanbietern in C immer noch eine (kleine) Falte hinzufügen.

Darüber hinaus haben dynamisch typisierte Sprachen wie Ruby, Python und Lisp normalerweise nicht den gleichen Ansatz für "Verträge" wie statisch typisierte Sprachen. In dynamisch typisierten Sprachen stellen Sie normalerweise nur eine Art "Gentleman-Vereinbarung" her, dass Code auf bestimmte Methoden reagiert, und wenn Ihre Klassen dieselbe Sprache zu sprechen scheinen, ist alles in Ordnung. Statisch typisierte Sprachen verfügen über zusätzliche Mechanismen, um diese Regeln beim Kompilieren durchzusetzen. In C # können Sie durch die Verwendung eines solchen Vertrags zumindest mäßig nützliche Garantien für die Einhaltung dieser Schnittstellen bereitstellen, sodass Sie Plugins und Substitutionen mit einem gewissen Maß an Garantie für die Gemeinsamkeit bündeln können, da Sie alle gegen denselben Vertrag kompilieren. In Ruby oder Scheme überprüfen Sie diese Vereinbarungen, indem Sie Tests schreiben, die zur Laufzeit funktionieren.

Diese Garantien für die Kompilierungszeit bieten einen messbaren Leistungsvorteil, da für einen Methodenaufruf kein doppelter Versand erforderlich ist. Um diese Vorteile in etwas wie Lisp, Ruby, JavaScript oder anderswo nutzen zu können, sind die heute noch leicht exotischen Mechanismen des statischen Just-in-Time-Kompilierens von Klassen in spezialisierten VMs erforderlich.

Eine Sache, für die das C # -Ökosystem noch relativ unausgereift ist, ist die Verwaltung dieser binären Abhängigkeiten. Java hat Maven seit mehreren Jahren damit beschäftigt, sicherzustellen, dass Sie alle erforderlichen Abhängigkeiten haben, während C # immer noch einen ziemlich primitiven MAKE-ähnlichen Ansatz verfolgt, bei dem Dateien strategisch vorzeitig am richtigen Ort platziert werden.

JasonTrue
quelle
1
In Bezug auf die Verwaltung von Abhängigkeiten sollten Sie sich NuGet ansehen . Hier ist ein schöner Artikel darüber von Phil Haack
Conrad Frix
In den von mir verwendeten R6RS-Schema-Implementierungen (z. B. Ikarus, Chez und Ypsilon) werden Abhängigkeiten basierend auf den Bibliotheksimporten automatisch behandelt. Die Abhängigkeiten werden gefunden und bei Bedarf für zukünftige Importe kompiliert und zwischengespeichert.
Dharmatech
Vertraut mit Nuget, und daher mein Kommentar, dass es "relativ unreif" ist
JasonTrue