En implementaciones del lenguaje de programación Scheme (estándar R6RS) puedo importar un módulo de la siguiente manera:
(import (abc def xyz))
El sistema intentará buscar un archivo $DIR/abc/def/xyz.sls
donde $DIR
haya algún directorio donde guarde sus módulos Scheme. xyz.sls
es el código fuente del módulo y se compila sobre la marcha si es necesario.
Los sistemas de módulos Ruby, Python y Perl son similares a este respecto.
C # por otro lado está un poco más involucrado.
Primero, tiene archivos dll a los que debe hacer referencia por proyecto. Debe hacer referencia a cada uno explícitamente. Esto es más complicado que decir, colocar archivos dll en un directorio y hacer que C # los recoja por su nombre.
Segundo, no hay una correspondencia de nombres uno a uno entre el nombre de archivo dll y los espacios de nombres ofrecidos por el dll. Puedo apreciar esta flexibilidad, pero también puede salirse de control (y lo ha hecho).
Para hacer esto concreto, sería bueno que, cuando digo esto using abc.def.xyz;
, C # intente encontrar un archivo abc/def/xyz.dll
, en algún directorio que C # sepa buscar (configurable por proyecto).
Encuentro que la forma de manejar módulos Ruby, Python, Perl, Scheme es más elegante. Parece que los lenguajes emergentes tienden a ir con el diseño más simple.
¿Por qué el mundo .NET / C # hace las cosas de esta manera, con un nivel adicional de indirección?
fuente
Respuestas:
La siguiente anotación en la Sección 3.3 de las Directrices de diseño del marco . Nombres de ensamblados y Dlls ofrece información sobre por qué los espacios de nombres y ensamblados están separados.
fuente
Agrega flexibilidad y permite cargar las bibliotecas (lo que llama módulos en su pregunta) a pedido.
Un espacio de nombres, múltiples bibliotecas:
Una de las ventajas es que puedo reemplazar fácilmente una biblioteca por otra. Digamos que tengo un espacio de nombres
MyCompany.MyApplication.DAL
y una bibliotecaDAL.MicrosoftSQL.dll
que contiene todas las consultas SQL y otras cosas que pueden ser específicas de la base de datos. Si quiero que la aplicación sea compatible con Oracle, solo agregoDAL.Oracle.dll
, manteniendo el mismo espacio de nombres. A partir de ahora, puedo entregar la aplicación con una biblioteca para clientes que necesitan la compatibilidad con Microsoft SQL Server, y con la otra biblioteca para clientes que usan Oracle.Cambiar el espacio de nombres en este nivel conduciría a un código duplicado o a la necesidad de cambiar todos los
using
s dentro del código fuente de cada base de datos.Una biblioteca, múltiples espacios de nombres:
Tener varios espacios de nombres en una biblioteca también es ventajoso en términos de legibilidad. Si, en una clase, uso solo uno de los espacios de nombres, pongo este en la parte superior del archivo.
Tener todos los espacios de nombres de una gran biblioteca sería bastante confuso tanto para la persona que lee el código fuente como para la propia escritora, ya que Intellisense tiene demasiadas cosas para sugerir en un contexto dado.
Al tener bibliotecas más pequeñas, una biblioteca por archivo tendría un impacto en el rendimiento: cada biblioteca debe cargarse a demanda en la memoria y procesarse por la máquina virtual cuando se ejecuta la aplicación; Menos archivos para cargar significa un rendimiento ligeramente mejor.
fuente
Parece que está eligiendo sobrecargar la terminología de "espacio de nombres" y "módulo". No debería sorprender que vea las cosas como "indirectas" cuando no se ajustan a sus definiciones.
En la mayoría de los lenguajes que admiten espacios de nombres, incluido C #, un espacio de nombres no es un módulo. Un espacio de nombres es una forma de definir los nombres. Los módulos son una forma de comportamiento de alcance.
En general, mientras que el tiempo de ejecución .Net admite la idea de un módulo (con una definición ligeramente diferente a la que está usando implícitamente), rara vez se usa; Solo lo he visto utilizado en proyectos creados en SharpDevelop, principalmente para que pueda construir una sola DLL a partir de módulos construidos en diferentes idiomas. En cambio, construimos bibliotecas usando una biblioteca vinculada dinámicamente.
En C #, los espacios de nombres se resuelven sin ninguna "capa de indirección" siempre y cuando todos estén en el mismo binario; cualquier indirección requerida es responsabilidad del compilador y el vinculador en el que no tiene que pensar mucho. Una vez que comienza a construir un proyecto con múltiples dependencias, hace referencia a bibliotecas externas. Una vez que su proyecto ha hecho referencia a una biblioteca externa (DLL), el compilador lo encuentra por usted.
En Scheme, si necesita cargar una biblioteca externa, debe hacer algo como
(#%require (lib "mylib.ss"))
primero, o usar la interfaz de función externa directamente, según recuerdo. Si está utilizando binarios externos, tiene la misma cantidad de trabajo para resolver los binarios externos. Lo más probable es que haya utilizado bibliotecas en su mayoría de manera tan común que hay un calce basado en esquemas que lo abstrae de usted, pero si alguna vez tiene que escribir su propia integración con una biblioteca de terceros, esencialmente tendrá que hacer un trabajo para "cargar " la biblioteca.En Ruby, los módulos, los espacios de nombres y los nombres de archivos en realidad están mucho menos conectados de lo que parece suponer; LOAD_PATH hace las cosas un poco complicadas, y las declaraciones de módulos pueden estar en cualquier lugar. Python probablemente esté más cerca de hacer las cosas de la manera que crees que estás viendo en Scheme, excepto que las bibliotecas de terceros en C aún agregan una arruga (pequeña).
Además, los lenguajes de tipo dinámico como Ruby, Python y Lisp generalmente no tienen el mismo enfoque para los "contratos" que los lenguajes de tipo estático. En los idiomas de tipo dinámico, generalmente solo establece una especie de "acuerdo de caballeros" de que el código responderá a ciertos métodos, y si sus clases parecen hablar el mismo idioma, todo está bien. Los lenguajes de tipo estático tienen mecanismos adicionales para hacer cumplir estas reglas en tiempo de compilación. En C #, el uso de dicho contrato le permite proporcionar al menos garantías moderadamente útiles de adhesión a estas interfaces, lo que le permite agrupar complementos y sustituciones con cierto grado de garantía de coincidencia porque todos compilan contra el mismo contrato. En Ruby o Scheme, verifica estos acuerdos escribiendo pruebas que funcionan en tiempo de ejecución.
Hay un beneficio de rendimiento medible de estas garantías de tiempo de compilación, ya que la invocación de un método no requiere doble envío. Para obtener estos beneficios en algo como Lisp, Ruby, JavaScript u otro lugar, ahora se requieren mecanismos exóticos de clases de compilación estática justo a tiempo en máquinas virtuales especializadas.
Una cosa para la que el ecosistema C # todavía tiene un soporte relativamente inmaduro es la gestión de estas dependencias binarias; Java ha tenido que lidiar con Maven durante varios años para asegurarse de que tenga todas sus dependencias necesarias, mientras que C # todavía tiene un enfoque bastante primitivo similar a MAKE que implica colocar estratégicamente los archivos en el lugar correcto con anticipación.
fuente