Parece que você está optando por sobrecarregar a terminologia do "namespace" e do "módulo". Não deve surpreender que você veja as coisas como "indiretas" quando elas não se encaixam nas suas definições.
Na maioria dos idiomas que oferecem suporte a espaços para nome, incluindo C #, um espaço para nome não é um módulo. Um espaço para nome é uma maneira de definir nomes de escopo. Módulos são uma forma de escopo de comportamento.
Em geral, embora o tempo de execução .Net suporte a idéia de um módulo (com uma definição ligeiramente diferente daquela que você está usando implicitamente), é raramente usado; Eu só vi isso usado em projetos criados no SharpDevelop, principalmente para que você pudesse criar uma única DLL a partir de módulos criados em diferentes idiomas. Em vez disso, construímos bibliotecas usando uma biblioteca vinculada dinamicamente.
No C #, os namespaces são resolvidos sem nenhuma "camada de indireção", desde que todos estejam no mesmo binário; qualquer indireção necessária é de responsabilidade do compilador e vinculador que você não precisa pensar muito. Depois de começar a criar um projeto com várias dependências, você faz referência a bibliotecas externas. Depois que o seu projeto faz uma referência a uma biblioteca externa (DLL), o compilador a encontra para você.
No esquema, se você precisar carregar uma biblioteca externa, precisará fazer algo como (#%require (lib "mylib.ss"))
primeiro ou usar a interface de função externa diretamente, pelo que me lembro. Se você estiver usando binários externos, terá a mesma quantidade de trabalho para resolver binários externos. É provável que você tenha usado bibliotecas na maioria das vezes tão usadas que existe um calço baseado em esquema que abstrai isso de você, mas se você precisar escrever sua própria integração com uma biblioteca de terceiros, terá que trabalhar para "carregar" " a biblioteca.
No Ruby, Módulos, Espaços de Nomes e Nomes de Arquivos são realmente muito menos conectados do que você imagina; o LOAD_PATH torna as coisas um pouco complicadas e as declarações do módulo podem estar em qualquer lugar. O Python provavelmente está mais perto de fazer as coisas da maneira que você pensa que está vendo no Scheme, exceto que as bibliotecas de terceiros em C ainda adicionam uma (pequena) ruga.
Além disso, linguagens dinamicamente tipadas como Ruby, Python e Lisp normalmente não têm a mesma abordagem de "contratos" que linguagens estaticamente tipificadas. Em idiomas tipificados dinamicamente, você geralmente estabelece apenas uma espécie de "acordo dos cavalheiros" de que o código responderá a certos métodos e, se suas classes parecerem estar falando o mesmo idioma, tudo estará bem. Linguagens de tipo estático possuem mecanismos adicionais para impor essas regras em tempo de compilação. No C #, o uso de um contrato desse tipo permite fornecer pelo menos garantias de adesão moderadamente úteis a essas interfaces, o que permite agrupar plugins e substituições com algum grau de garantia de semelhança, pois todos compilam com o mesmo contrato. Em Ruby ou Scheme, você verifica esses contratos escrevendo testes que funcionam em tempo de execução.
Há um benefício de desempenho mensurável com essas garantias de tempo de compilação, pois uma chamada de método não requer expedição dupla. Para obter esses benefícios em algo como Lisp, Ruby, JavaScript ou qualquer outro lugar, o que agora são mecanismos ainda exóticos de classes compiladas estaticamente just-in-time em VMs especializadas é necessário.
Uma coisa para a qual o ecossistema C # ainda tem suporte relativamente imaturo é o gerenciamento dessas dependências binárias; O Java possui o Maven há vários anos para garantir que você tenha todas as suas dependências necessárias, enquanto o C # ainda possui uma abordagem bastante primitiva do tipo MAKE, que envolve a colocação estratégica de arquivos no lugar certo antes do tempo.