¿Por qué los módulos .NET separan los nombres de los archivos del módulo de los espacios de nombres?

9

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.slsdonde $DIRhaya algún directorio donde guarde sus módulos Scheme. xyz.slses 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?

dharmatech
fuente
10
El mecanismo de resolución de clase y ensamblaje de .NET ha funcionado bien durante más de 10 años. Creo que te falta un malentendido fundamental (o de investigación no es suficiente) sobre por qué está diseñado de esa manera - por ejemplo, para apoyar la reorientación de montaje, etc durante el enlace y muchos otros mecanismos útiles resolución
Kev
Estoy bastante seguro de que la resolución DLL de una declaración de uso rompería la ejecución de lado a lado. Además, si hubiera un 1 a 1, necesitaría 50 dlls para todos los espacios de nombres en mscorlib, o tendrían que abandonar la idea de los espacios de nombres
Conrad Frix
¿Un nivel extra de indirección? Hmmmmm .... dmst.aueb.gr/dds/pubs/inbook/beautiful_code/html/Spi07g.html
user541686
@gilles ¡Gracias por editar y mejorar la pregunta!
dharmatech
Algunos de sus puntos son peculiares de Visual Studio, y compararlos con un idioma no es del todo justo ( por ejemplo , referencias de DLL en Proyectos). Una mejor comparación para el entorno completo de .NET sería Java + Eclipse.
Ross Patterson

Respuestas:

22

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.

BRAD ABRAMS Al principio del diseño del CLR, decidimos separar la vista del desarrollador de la plataforma (espacios de nombres) de la vista de empaquetado y despliegue de la plataforma (ensamblajes). Esta separación permite que cada uno se optimice de forma independiente según sus propios criterios. Por ejemplo, somos libres de factorizar espacios de nombres para agrupar tipos que están relacionados funcionalmente (p. Ej., Todas las cosas de E / S en System.IO), mientras que los ensambles se pueden factorizar por razones de rendimiento (tiempo de carga), implementación, servicio o versiones. .

Conrad Frix
fuente
Parece la fuente más autorizada hasta ahora. Gracias Conrad!
dharmatech
+1 por obtener la maldita respuesta autoritaria. Pero también, cada pregunta de " por qué C # hace <X> " también debe verse a través de la lente de " Java does <X> de la siguiente manera: ... ", porque C # fue (explícitamente o no) una reacción a Sun y Las diferentes agendas de Microsoft para Java en Windows.
Ross Patterson
6

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.DALy una biblioteca DAL.MicrosoftSQL.dllque 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 agrego DAL.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 usings 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.

Arseni Mourzenko
fuente
El segundo caso no requiere que los nombres de los archivos estén separados de los espacios de nombres (aunque permitir tal separación hace que la separación sea significativamente más fácil), ya que un conjunto dado puede tener fácilmente muchas subcarpetas y archivos. Además, también es posible incrustar múltiples ensamblajes en la misma DLL (por ejemplo, usando ILMerge ). Java funciona adopta este enfoque.
Brian
2

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.

JasonTrue
fuente
1
Con respecto a la gestión de dependencias, es posible que desee echar un vistazo a NuGet . Aquí hay un buen artículo de Phil Haack
Conrad Frix
En las implementaciones del Esquema R6RS que he usado (Ikarus, Chez e Ypsilon, por ejemplo), las dependencias se manejan automáticamente, en función de las importaciones de la biblioteca. Las dependencias se encuentran y, si es necesario, se compilan y almacenan en caché para futuras importaciones.
dharmatech
Familiarizado con Nuget, y por lo tanto mi comentario de que es "relativamente inmaduro"
JasonTrue