Como a Microsoft criou assemblies que possuem referências circulares?

107

No .NET BCL, existem referências circulares entre:

  • System.dll e System.Xml.dll
  • System.dll e System.Configuration.dll
  • System.Xml.dll e System.Configuration.dll

Aqui está uma captura de tela do .NET Reflector que mostra o que quero dizer:

insira a descrição da imagem aqui

Como a Microsoft criou esses assemblies é um mistério para mim. É necessário um processo de compilação especial para permitir isso? Eu imagino que algo interessante está acontecendo aqui.

Drew Noakes
fonte
2
Ótima pergunta. Nunca tive tempo para inspecionar isso, mas estou curioso para saber a resposta. Na verdade, parece que Dykam forneceu um sensato.
Noldorin
3
por que essas dll não são mescladas em uma, se todas exigem uma da outra? existe alguma razão prática para isso?
Andreas Petersson
1
Pergunta interessante ... Gostaria de saber a resposta de Eric Lippert para esta! E como Andreas disse, eu me pergunto por que eles não colocaram tudo na mesma montagem ...
Thomas Levesque
Bem, se uma montagem precisa ser atualizada, eles não precisarão tocar nas outras. Essa é a única razão que vejo. Pergunta interessante
Atmocreations
2
Dê uma olhada nesta apresentação (arquivos asmmeta): msakademik.net/academicdays2005/Serge_Lidin.ppt
Mehrdad

Respostas:

58

Só posso dizer como o Projeto Mono faz isso. O teorema é bastante simples, embora confunda o código.

Eles primeiro compilam System.Configuration.dll, sem que a parte precise da referência a System.Xml.dll. Depois disso, eles compilam System.Xml.dll da maneira normal. Agora vem a magia. Eles recompilaram System.configuration.dll, com a parte precisando da referência a System.Xml.dll. Agora há uma compilação bem-sucedida com a referência circular.

Em resumo:

  • A é compilado sem que o código precise de B e a referência a B.
  • B é compilado.
  • A é recompilado.
Dykam
fonte
1
É bloqueado pelo Visual Studio, mas pode ser feito usando o compilador de linha de comando (csc.exe) diretamente. Veja minha resposta.
Alfred Myers
14
Eu sei. O principal sistema de compilação do Mono não é o Visual Studio. Acho que a Microsofts também não.
Dykam
35

RBarryYoung e Dykam estão no caminho certo. A Microsoft usa uma ferramenta interna que usa ILDASM para desmontar assemblies, remover todas as coisas internas / privadas e corpos de método e recompilar IL novamente (usando ILASM) no que é chamado de 'montagem desidratada' ou montagem de metadados. Isso é feito toda vez que a interface pública da montagem é alterada.

Durante a construção, assemblies de metadados são usados ​​em vez de reais. Dessa forma, o ciclo é quebrado.

Srdjan Jovcic
fonte
1
Resposta interessante, você tem algum link?
Henk Holterman
Estou tentando encontrar uma referência externa para a ferramenta. Não creio que seja publicado fora da Microsoft, mas o conceito é simples: desmontar-retirar partes internas-remontar.
Srdjan Jovcic
Concordo - resposta interessante. Alguns links para fazer o backup seriam bons.
Drew Noakes em
Sim, é realmente assim que é feito (por experiência pessoal).
Pavel Minaev
1
Eles não são fortemente assinados até depois da construção (eles são assinados com atraso), portanto, os conjuntos desidratados não são assinados.
Srdjan Jovcic
26

Isso pode ser feito da maneira que Dykam descreveu, mas o Visual Studio impede você de fazê-lo.

Você terá que usar o compilador de linha de comando csc.exe diretamente.

  1. csc / target: biblioteca ClassA.cs

  2. csc / target: biblioteca ClassB.cs /reference:ClassA.dll

  3. csc / target: biblioteca ClassA.cs ClassC.cs /reference:ClassB.dll


//ClassA.cs
namespace CircularA {
    public class ClassA {
    }
}


//ClassB.cs
using CircularA;
namespace CircularB {
    public class ClassB : ClassA  {
    }
}


//ClassC.cs
namespace CircularA {
    class ClassC : ClassB {
    }
}
Alfred Myers
fonte
Você também pode fazer isso no Visual Studio, embora seja bastante difícil também. A maneira básica é usar # if's e remover a referência usando o explorador de soluções, revertendo isso na terceira etapa. Uma outra maneira que estou pensando é um terceiro arquivo de projeto incluindo os mesmos arquivos, mas referências diferentes. Isso funcionaria porque você pode especificar a ordem de construção.
Dykam
Pelo que eu sei, não posso testar aqui.
Dykam
Eu realmente gostaria de ver isso. Pelo que experimentei aqui, no momento em que você tenta adicionar referência, o IDE o impede.
Alfred Myers
Eu sei. Mas um terceiro projeto não tem aquela referência E o símbolo #if, e ser referenciado pelo segundo, que é referenciado pelo primeiro. Sem ciclo. Mas o terceiro usa o código do primeiro e dá saída no primeiro local de montagem. um conjunto pode ser facilmente substituído por outro com as mesmas especificações. Mas acho que o nome forte pode causar um problema neste método.
Dykam
É um pouco como a resposta de Srdjan, embora seja um método diferente.
Dykam
18

É muito fácil de fazer no Visual Studio, desde que você não use referências de projeto ... Experimente isto:

  1. Estúdio visual aberto
  2. Crie 2 projetos de biblioteca de classes "ClassLibrary1" e "ClassLibrary2".
  3. Construir
  4. Em ClassLibrary1, adicione uma referência a ClassLibrary2 navegando até a dll criada na etapa 3.
  5. Em ClassLibrary2, adicione uma referência a ClassLibrary1 navegando até a dll criada na etapa 3.
  6. Compilar novamente (Observação: se você fizer alterações em ambos os projetos, precisará compilar duas vezes para tornar ambas as referências "novas")

Então é assim que você faz. Mas falando sério ... Nunca faça isso em um projeto real! Se você fizer isso, o Papai Noel não vai lhe trazer nenhum presente este ano.

JohannesH
fonte
1
A única exceção é se for entre 26 e 31 de dezembro e os presentes já tiverem sido garantidos
Jesse Hufstetler
6

Acho que isso poderia ser feito começando com um conjunto acíclico de assemblies e usando ILMerge para unir os assemblies menores em grupos logicamente relacionados.

Steve Gilham
fonte
4

Bem, eu nunca fiz isso no Windows, mas fiz em muitos dos ambientes compile-link-rtl que serviram como progenitores práticos para ele. O que você faz é primeiro fazer stub "alvos" sem as referências cruzadas, em seguida, vincular, adicionar as referências circulares e, em seguida, vincular novamente. Os linkers geralmente não se preocupam com refs circulares ou cadeias de refs seguintes, eles só se preocupam em resolver cada referência por conta própria.

Portanto, se você tiver duas bibliotecas, A e B, que precisam fazer referência uma à outra, tente algo assim:

  1. Link A sem quaisquer referências a B.
  2. Link B com referências a A.
  3. Link A, adicionando os refs a B.

Dykam faz um bom ponto, é compilar, não vincular em .Net, mas o princípio permanece o mesmo: Faça suas fontes com referência cruzada, com seus pontos de entrada exportados, mas com todos, exceto um, tendo suas próprias referências aos outros esboçados Fora. Construa-os assim. Em seguida, remova as referências externas e reconstrua-as. Isso deve funcionar mesmo sem nenhuma ferramenta especial; na verdade, essa abordagem funcionou em todos os sistemas operacionais em que já experimentei (cerca de 6 deles). Embora obviamente algo que o automatize seria uma grande ajuda.

RBarryYoung
fonte
o teorema está certo. Porém, no mundo .Net, a vinculação é feita de forma dinâmica e não é um problema. É a etapa de compilação em que essa solução é necessária.
Dykam
Desculpe por consertar você de novo: P. Mas a referência (vinculação) em tempo de compilação acontece no mundo .Net, que é tudo o que é derivado dessa especificação ECMA específica. Assim, Mono, dotGnu e .Net. Não o próprio Windows.
Dykam
1

Uma abordagem possível é usar a compilação condicional (#if) para primeiro compilar um System.dll que não depende desses outros assemblies, em seguida, compilar os outros assemblies e, por último, recompilar System.dll para incluir as partes que dependem de Xml e Configuração.

Daniel
fonte
1
Infelizmente, isso não permite que você referencie condicionalmente um assembly (eu gostaria que fosse possível, isso realmente ajudaria em um de meus projetos ...)
Thomas Levesque
1
As referências condicionais podem ser feitas facilmente editando o arquivo .csproj. Basta adicionar um atributo Condition ao elemento <Reference>.
Daniel
0

Tecnicamente, é possível que eles não tenham sido compilados e montados manualmente. Afinal, são bibliotecas de baixo nível.

tylermac
fonte
Na verdade não. Não há muitas coisas de baixo nível nele, apenas básicas. O que te fez pensar que seria de baixo nível? O tempo de execução e o corlib são de baixo nível. Relativamente. Ainda simples C ou C ++, embora o JIT contenha coisas de baixo nível.
Dykam