quinta-feira, 30 de dezembro de 2010

Sabe o que é modificador internal? Sabe mesmo???

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 Projetoprojetos, 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 cBuild Faillasse 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:

  1. O tipo AssemblyB.MyInternalClass não tem construtor definido.
  2. 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.

AssemblyInfo

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 MyConsoleApplicationMyInternalClass 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 projetoSistem.Workflow.Activities 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

Nenhum comentário: