Pourquoi les modules .NET séparent-ils les noms de fichier de module des espaces de noms?


9

Dans les implémentations du langage de programmation Scheme (standard R6RS), je peux importer un module comme suit:

(import (abc def xyz))

Le système essaiera de rechercher un fichier $DIR/abc/def/xyz.slsoù se $DIRtrouve un répertoire où vous conservez vos modules Scheme. xyz.slsest le code source du module et il est compilé à la volée si nécessaire.

Les systèmes de modules Ruby, Python et Perl sont similaires à cet égard.

C # d'autre part est un peu plus impliqué.

Tout d'abord, vous avez des fichiers dll que vous devez référencer par projet. Vous devez référencer chacun explicitement. C'est plus compliqué que de dire, déposer des fichiers dll dans un répertoire et demander à C # de les récupérer par leur nom.

Deuxièmement, il n'y a pas de correspondance de dénomination univoque entre le nom de fichier dll et les espaces de noms offerts par la dll. Je peux apprécier cette flexibilité, mais elle peut aussi devenir incontrôlable (et a).

Pour rendre cela concret, ce serait bien si, quand je dis cela using abc.def.xyz;, C # essaierait de trouver un fichier abc/def/xyz.dll, dans un répertoire dans lequel C # sait regarder (configurable par projet).

Je trouve la manière Ruby, Python, Perl, Scheme de gérer les modules plus élégante. Il semble que les langues émergentes ont tendance à aller avec la conception plus simple.

Pourquoi le monde .NET / C # fait-il les choses de cette façon, avec un niveau d'indirection supplémentaire?


10
L'assemblage et le mécanisme de résolution de classe de .NET fonctionnent très bien depuis plus de 10 ans. Je pense que vous manquez d'un malentendu fondamental (ou pas assez de recherche) sur la raison pour laquelle il est conçu de cette façon - par exemple pour soutenir la redirection d'assemblage, etc. au moment de la liaison et de nombreux autres mécanismes de résolution utiles
Kev

Je suis à peu près sûr que la résolution de DLL à partir d'une instruction using interromprait l'exécution côte à côte. De plus, s'il y avait un 1 à 1, vous auriez besoin de 50 DLL pour tous les espaces de noms dans mscorlib, ou ils devraient abandonner l'idée des espaces de noms
Conrad Frix

Un niveau d'indirection supplémentaire? Hmmmmm .... dmst.aueb.gr/dds/pubs/inbook/beautiful_code/html/Spi07g.html
user541686

@gilles Merci pour l'édition et l'amélioration de la question!
dharmatech

Certains de vos points sont propres à Visual Studio et les comparer à un langage n'est pas tout à fait juste ( par exemple , les références DLL dans les projets). Une meilleure comparaison pour l'environnement .NET complet serait Java + Eclipse.
Ross Patterson

Réponses:


22

L'annotation suivante dans la section 3.3 des directives de conception du cadre . Les noms des assemblys et des DLL permettent de comprendre pourquoi les espaces de noms et les assemblys sont séparés.

BRAD ABRAMS Dès le début de la conception du CLR, nous avons décidé de séparer la vue développeur de la plateforme (espaces de noms) de la vue packaging et déploiement de la plateforme (assemblages). Cette séparation permet à chacun d'être optimisé indépendamment en fonction de ses propres critères. Par exemple, nous sommes libres de factoriser les espaces de noms pour regrouper les types qui sont fonctionnellement liés (par exemple, tous les éléments d'E / S dans System.IO) tandis que les assemblys peuvent être pris en compte pour des raisons de performances (temps de chargement), de déploiement, de maintenance ou de version. .


Semble être la source la plus fiable à ce jour. Merci Conrad!
dharmatech

+1 pour avoir obtenu la réponse maudite et faisant autorité. Mais aussi, chaque question " pourquoi C # fait-il <X> " doit également être vue à travers l'objectif de " Java fait <X> comme suit: ... ", parce que C # était (explicitement ou non) une réaction à Sun et à Les différents agendas de Microsoft pour Java sur Windows.
Ross Patterson

6

Il ajoute de la flexibilité et permet de charger les bibliothèques (ce que vous appelez des modules dans votre question) à la demande.

Un espace de noms, plusieurs bibliothèques:

L'un des avantages est que je peux facilement remplacer une bibliothèque par une autre. Disons que j'ai un espace de noms MyCompany.MyApplication.DALet une bibliothèque DAL.MicrosoftSQL.dll, qui contient toutes les requêtes SQL et autres éléments qui peuvent être spécifiques à la base de données. Si je veux que l'application soit compatible avec Oracle, j'ajoute simplement en DAL.Oracle.dllgardant le même espace de noms. À partir de maintenant, je peux fournir l'application avec une bibliothèque pour les clients qui ont besoin de la compatibilité avec Microsoft SQL Server et avec l'autre bibliothèque pour les clients qui utilisent Oracle.

La modification de l'espace de noms à ce niveau entraînerait soit un code en double, soit la nécessité d'aller modifier tous les usings à l'intérieur du code source pour chaque base de données.

Une bibliothèque, plusieurs espaces de noms:

Avoir plusieurs espaces de noms dans une bibliothèque est également avantageux en termes de lisibilité. Si, dans une classe, j'utilise un seul des espaces de noms, je mets juste celui-ci en haut du fichier.

  • Avoir tous les espaces de noms d'une grande bibliothèque serait plutôt déroutant à la fois pour la personne qui lit le code source et pour l'auteur lui-même, Intellisense ayant trop de choses à suggérer dans un contexte donné.

  • Avec des bibliothèques plus petites, une bibliothèque par fichier aurait un impact sur les performances: chaque bibliothèque doit être chargée à la demande en mémoire et traitée par la machine virtuelle lors de l'exécution de l'application; moins de fichiers à charger signifie des performances légèrement meilleures.


Le deuxième cas ne nécessite pas que les noms de fichiers soient séparés des espaces de noms (bien que permettre une telle séparation facilite considérablement la séparation), car un assemblage donné peut facilement avoir de nombreux sous-dossiers et fichiers. De plus, il est également possible d'incorporer plusieurs assemblys dans la même DLL (par exemple, en utilisant ILMerge ). Java Works adopte cette approche.
Brian

2

Il semble que vous choisissiez de surcharger la terminologie de "namespace" et de "module". Il ne devrait pas être surprenant que vous voyiez les choses comme "indirectes" quand elles ne correspondent pas à vos définitions.

Dans la plupart des langages qui prennent en charge les espaces de noms, y compris C #, un espace de noms n'est pas un module. Un espace de noms est un moyen de délimiter les noms. Les modules sont un moyen de délimiter le comportement.

En général, alors que le runtime .Net prend en charge l'idée d'un module (avec une définition légèrement différente de celle que vous utilisez implicitement), il est plutôt rarement utilisé; Je ne l'ai vu qu'utilisé dans des projets construits dans SharpDevelop, principalement pour que vous puissiez créer une seule DLL à partir de modules construits dans différents langages. Au lieu de cela, nous créons des bibliothèques à l'aide d'une bibliothèque liée dynamiquement.

En C #, les espaces de noms se résolvent sans "couche d'indirection" tant qu'ils sont tous dans le même binaire; toute indirection requise est une responsabilité du compilateur et de l'éditeur de liens à laquelle vous n'avez pas à réfléchir. Une fois que vous avez commencé à créer un projet avec plusieurs dépendances, vous référencez ensuite des bibliothèques externes. Une fois que votre projet a fait référence à une bibliothèque externe (DLL), le compilateur la trouve pour vous.

Dans Scheme, si vous devez charger une bibliothèque externe, vous devez faire quelque chose comme d' (#%require (lib "mylib.ss"))abord, ou utiliser directement l'interface de fonction étrangère, si je me souviens bien. Si vous utilisez des binaires externes, vous avez la même quantité de travail pour résoudre les binaires externes. Il y a de fortes chances que vous ayez principalement utilisé des bibliothèques si couramment utilisées qu'il existe un module d'interface basé sur Scheme qui vous en fait abstraction, mais si vous devez écrire votre propre intégration avec une bibliothèque tierce, vous devrez essentiellement faire un peu de travail pour "charger" " la bibliothèque.

Dans Ruby, les modules, les espaces de noms et les noms de fichiers sont en réalité beaucoup moins connectés que vous ne le pensez. LOAD_PATH rend les choses un peu compliquées et les déclarations de module peuvent être n'importe où. Python est probablement plus proche de faire les choses comme vous pensez que vous voyez dans Scheme, sauf que les bibliothèques tierces en C ajoutent toujours une (petite) ride.

De plus, les langages typés dynamiquement comme Ruby, Python et Lisp n'ont généralement pas la même approche des "contrats" que les langages typés statiquement. Dans les langages typés dynamiquement, vous n'établissez généralement qu'une sorte de "Gentleman's agreement" que le code répondra à certaines méthodes, et si vos classes semblent parler le même langage, tout va bien. Les langages typés statiquement ont des mécanismes supplémentaires pour appliquer ces règles au moment de la compilation. En C #, l'utilisation d'un tel contrat vous permet de fournir des garanties d'adhésion au moins modérément utiles à ces interfaces, ce qui vous permet de regrouper des plugins et des substitutions avec un certain degré de garantie de similitude car vous compilez tous avec le même contrat. Dans Ruby ou Scheme, vous vérifiez ces accords en écrivant des tests qui fonctionnent au moment de l'exécution.

Ces garanties de temps de compilation présentent un avantage mesurable en termes de performances, car une invocation de méthode ne nécessite aucune double répartition. Afin d'obtenir ces avantages dans quelque chose comme Lisp, Ruby, JavaScript ou ailleurs, ce qui est maintenant des mécanismes encore un peu exotiques de compilation statique juste à temps de classes dans des machines virtuelles spécialisées est requis.

Une chose que l'écosystème C # a encore un support relativement immature est la gestion de ces dépendances binaires; Java a eu Maven pendant plusieurs années pour s'assurer que vous disposez de toutes les dépendances requises, tandis que C # a toujours une approche assez primitive de type MAKE qui implique de placer les fichiers stratégiquement au bon endroit à l'avance.


1
En ce qui concerne la gestion des dépendances, vous voudrez peut-être jeter un œil à NuGet . Voici un bel article à ce sujet de Phil Haack
Conrad Frix

Dans les implémentations du schéma R6RS que j'ai utilisées (Ikarus, Chez et Ypsilon, par exemple), les dépendances sont gérées automatiquement, en fonction des importations de bibliothèque. Les dépendances sont trouvées et si nécessaire compilées et mises en cache pour les importations futures.
dharmatech

Familier avec Nuget, et donc mon commentaire qu'il est "relativement immature"
JasonTrue
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.