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.