Pois é, o post começa com algo bem básico do C#, coisa do primeiro dia de aula mesmo: Modificadores de Acesso. Só pra relembrar:
- public : Sem restrição de acesso.
- protected : Acessado pela classe que o define e pelas classes derivadas dela.
- internal : Acessado apenas de dentro do mesmo assembly.
- private : Acessado somente de dentro da classe (mesmo se forem instancias diferentes).
Isso todo mundo já sabe (ou deveriam saber). O ponto em que quero chegar é que, dizer que "membros ou tipos internal só podem ser acessados dentro do mesmo assembly" não é uma completa verdade.
InternalsVisibleToAttribute
O atributo InternalsVisibleTo introduz ao assembly o conceito de Friend Assembly. Com isto os membros ou tipos marcados com o modificador de acesso internal podem ser acessados de outro assembly. Vamos ver como funciona:
Nesta solução podemos ver que existem 3 projetos, sendo 2 projetos do tipo ClassLibrary e 1 ConsoleApplication.
Adicionamos o projeto AssemblyA como referencia no projeto MyConsoleApplication. Depois o projeto AssemblyB como referencia no projeto AssemblyA e MyConsoleApplication.
Ele ficará como a imagem ao lado. Podemos ver que existem algumas classes já declaradas.
No AssemblyB temos uma classe com modificador de acesso internal e outra com o modificador de acesso public (Como dizem os nomes).
Agora vamos tentar instanciar a classe AssemblyB.MyInternalClass de dentro da classe AssemblyA.MyPublicUtilClass e compilar a aplicação para ver o que acontece.
O Visual Studio não deixa compilar a aplicação. Ele nos mostra o motivo do build failed em duas mensagens de erro:
- O tipo AssemblyB.MyInternalClass não tem construtor definido.
- O tipo AssemblyB.MyInternalClass não é acessível devido ao seu nível de proteção.
Como a classe está definida com o seu modificador de acesso sendo internal o Visual Studio segue a regra que descrevemos mais acima e não deixa ela ser acessada (no caso instanciada).
Esses mesmos dois erros ocorreriam se tivéssemos tentado o acesso a esta classe no assembly MyConsoleApplication.
Nesse momento entra em ação o atributo InternalsVisibleTo que faz uma alteração a nível de assembly fazendo com que AssemblyA possa instanciar a classe em questão.
Segue o trecho que pode ser colocado em qualquer arquivo de código mas que por boas práticas deve ser colocado no arquivo AssemblyInfo da pasta Properties do projeto.
Com isto compilamos novamente a aplicação e podemos ver que agora temos acesso a classe MyInternalClass de dentro do assembly AssemblyA.
Também podemos verificar que apesar do acesso a classe MyInternalClass ter sido alterado em relação ao assembly AssemblyA, o assembly MyConsoleApplication ainda continua ser ter acesso. Se fosse necessário que o assembly MyConsoleApplication pudesse acessar também a classe MyInternalClass bastaria colocar mais um atributo (permite múltiplos) no AssemblyInfo especificando isto.
Considerações Importantes
Não são muitos os casos onde é aconselhada esta prática. Ela é exigida em casos onde a organização do projeto a exige:
- Casos onde existe um projeto de teste e é necessário o acesso a membros internal para o teste.
- Quando se desenvolve um ClassLibrary que é composto por vários assemblies mas requerem acesso a membros existentes entre eles. Este é o caso do assembly System.Workflow.Activities.
O exemplo que implementei foi o de mais fácil compreensão. Existem algumas regras que devem ser seguidas para a utilização desde atributo. A regra mais importante é a respeito dos Strong Names:
- Se o assembly que se quer ter os membros internal visíveis tiver um strong name o assembly que o consumirá também deverá ter. No atributo InternalsVisibleTo deverá constar, separados por vírgula, o nome do assembly mais a public key dentro da string que é passada ao construtor.
- Se o assembly que se quer ter os membros internal visíveis não tiver um strong name o assembly que o consumirá também não deverá ter.
Enjoy