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.sls
où se $DIR
trouve un répertoire où vous conservez vos modules Scheme. xyz.sls
est 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?
la source
Réponses:
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.
la source
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.DAL
et une bibliothèqueDAL.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 enDAL.Oracle.dll
gardant 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
using
s à 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.
la source
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.
la source