Sembra che tu stia scegliendo di sovraccaricare la terminologia sia del "namespace" che del "modulo". Non dovrebbe sorprendere il fatto che tu veda le cose come "indirette" quando non sono adatte alle tue definizioni.
Nella maggior parte delle lingue che supportano gli spazi dei nomi, incluso C #, uno spazio dei nomi non è un modulo. Uno spazio dei nomi è un modo di definire i nomi. I moduli sono un modo di osservare gli ambiti.
In generale, mentre il runtime .Net supporta l'idea di un modulo (con una definizione leggermente diversa da quella che si sta utilizzando implicitamente), è usato piuttosto raramente; L'ho visto solo in progetti sviluppati in SharpDevelop, principalmente per creare una singola DLL da moduli costruiti in lingue diverse. Invece, costruiamo librerie usando una libreria collegata dinamicamente.
In C #, gli spazi dei nomi si risolvono senza alcun "livello di riferimento indiretto" purché siano tutti nello stesso binario; qualsiasi indirezione richiesta è una responsabilità del compilatore e del linker a cui non devi pensare molto. Una volta che si inizia a costruire un progetto con più dipendenze, si fa riferimento alle librerie esterne. Una volta che il tuo progetto ha fatto riferimento a una libreria esterna (DLL), il compilatore lo trova per te.
In Scheme, se hai bisogno di caricare una libreria esterna, devi prima fare qualcosa come (#%require (lib "mylib.ss"))
, o usare direttamente l'interfaccia della funzione straniera, come ricordo. Se si utilizzano i binari esterni, si ha la stessa quantità di lavoro per risolvere i binari esterni. È probabile che tu abbia usato principalmente librerie così comunemente usate che esiste uno shim basato su Scheme che ti astrae da te, ma se dovessi mai scrivere la tua integrazione con una libreria di terze parti, in pratica avrai un po 'di lavoro da "caricare" "la biblioteca.
In Ruby, Modules, Namespace e Nomi file sono in realtà molto meno connessi di quanto sembri supporre; il LOAD_PATH rende le cose un po 'complicate e le dichiarazioni del modulo possono essere ovunque. Python è probabilmente più vicino a fare le cose come pensi di vedere in Scheme, tranne che le librerie di terze parti in C aggiungono ancora una (piccola) ruga.
Inoltre, linguaggi tipizzati dinamicamente come Ruby, Python e Lisp in genere non hanno lo stesso approccio ai "contratti" come lingue tipizzate staticamente. Nelle lingue digitate dinamicamente, di solito si stabilisce solo una sorta di "Gentleman's agreement" che il codice risponderà a determinati metodi, e se le tue classi sembrano parlare la stessa lingua, tutto va bene. Le lingue tipizzate staticamente dispongono di meccanismi aggiuntivi per applicare queste regole in fase di compilazione. In C #, l'utilizzo di tale contratto consente di fornire almeno moderatamente utili garanzie di aderenza a queste interfacce, che consente di raggruppare plug-in e sostituzioni con un certo grado di garanzia di comunanza perché tutti si compilano con lo stesso contratto. In Ruby o Scheme, si verificano questi accordi scrivendo test che funzionano in fase di runtime. Ci sono costi e benefici per entrambe le soluzioni, ma non è d'aiuto l'idea di uno spazio dei nomi, un modulo e una libreria.
C'è un vantaggio misurabile in termini di prestazioni da queste garanzie di tempo di compilazione, poiché l'invocazione di un metodo non richiede una doppia spedizione. Per ottenere questi benefici in qualcosa come Lisp, Ruby, JavaScript o altrove, sono ora necessari meccanismi leggermente esotici di classi di compilazione statica just-in-time in macchine virtuali specializzate.
Una cosa su cui l'ecosistema C # ha ancora un supporto relativamente immaturo è la gestione di queste dipendenze binarie; Java ha avuto Maven per diversi anni per far sì che tu abbia tutte le tue dipendenze necessarie, mentre C # ha ancora un approccio MAKE abbastanza primitivo che implica posizionare strategicamente i file nel posto giusto prima del tempo.