Nas implementações da linguagem de programação Scheme (padrão R6RS), posso importar um módulo da seguinte maneira:
(import (abc def xyz))
O sistema tentará procurar um arquivo $DIR/abc/def/xyz.sls
em $DIR
algum diretório em que você mantenha seus módulos do esquema. xyz.sls
é o código fonte do módulo e é compilado em tempo real, se necessário.
Os sistemas de módulos Ruby, Python e Perl são semelhantes nesse aspecto.
C #, por outro lado, é um pouco mais envolvido.
Primeiro, você tem arquivos dll que devem ser referenciados por projeto. Você deve fazer referência a cada um explicitamente. Isso está mais envolvido do que, digamos, soltar arquivos DLL em um diretório e fazer com que o C # os escolha pelo nome.
Segundo, não há uma correspondência de nomes individual entre o nome do arquivo da DLL e os espaços de nomes oferecidos pela DLL. Eu posso apreciar essa flexibilidade, mas também pode ficar fora de controle (e tem).
Para tornar isso concreto, seria bom se, quando digo isso using abc.def.xyz;
, o C # tentasse encontrar um arquivo abc/def/xyz.dll
, em algum diretório que o C # saiba procurar (configurável por projeto).
Acho a maneira Ruby, Python, Perl, Scheme de lidar com módulos mais elegante. Parece que as línguas emergentes tendem a seguir o design mais simples.
Por que o mundo .NET / C # faz as coisas dessa maneira, com um nível extra de indireção?
fonte
Respostas:
A seguinte anotação na Seção 3.3 das Diretrizes de Projeto da Estrutura . O Names of Assemblies and Dlls oferece informações sobre por que namespaces e assemblies são separados.
fonte
Ele adiciona flexibilidade e permite carregar as bibliotecas (o que você chama de módulos em sua pergunta) sob demanda.
Um espaço para nome, várias bibliotecas:
Uma das vantagens é que posso substituir facilmente uma biblioteca por outra. Digamos que eu tenho um espaço para nome
MyCompany.MyApplication.DAL
e uma bibliotecaDAL.MicrosoftSQL.dll
, que contém todas as consultas SQL e outras coisas que podem ser específicas ao banco de dados. Se eu quiser que o aplicativo seja compatível com o Oracle, basta adicionarDAL.Oracle.dll
, mantendo o mesmo espaço para nome. A partir de agora, posso entregar o aplicativo com uma biblioteca para clientes que precisam da compatibilidade com o Microsoft SQL Server e com a outra biblioteca para clientes que usam Oracle.Alterar o espaço para nome nesse nível levaria à duplicação do código ou à necessidade de alterar todos os
using
s dentro do código-fonte de cada banco de dados.Uma biblioteca, vários espaços para nome:
Ter vários namespaces em uma biblioteca também é vantajoso em termos de legibilidade. Se, em uma classe, eu usar apenas um dos namespaces, coloquei apenas este na parte superior do arquivo.
Ter todos os espaços para nome de uma grande biblioteca seria bastante confuso tanto para a pessoa que leu o código-fonte quanto para o próprio escritor, com o Intellisense tendo muitas coisas para sugerir em um determinado contexto.
Com bibliotecas menores, uma biblioteca por arquivo teria um impacto no desempenho: cada biblioteca deve ser carregada sob demanda na memória e processada pela máquina virtual ao executar o aplicativo; menos arquivos para carregar significa um desempenho um pouco melhor.
fonte
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.
fonte