Devem as diretivas ‘using’ estar dentro ou fora do namespace?

Eu tenho executado o StyleCop sobre algum código C #, e ele continua relatando que minhas diretivas de using devem estar dentro do namespace.

Existe uma razão técnica para colocar as diretivas using dentro e não fora do namespace?

Na verdade, existe uma diferença (sutil) entre os dois. Imagine que você tenha o seguinte código em File1.cs:

 // File1.cs using System; namespace Outer.Inner { class Foo { static void Bar() { double d = Math.PI; } } } 

Agora imagine que alguém adicione outro arquivo (File2.cs) ao projeto que se parece com isso:

 // File2.cs namespace Outer { class Math { } } 

O compilador procura Outer antes de olhar para aqueles using diretivas fora do namespace, portanto, ele localiza Outer.Math vez de System.Math . Infelizmente (ou talvez felizmente?), Outer.Math não tem nenhum membro PI , então File1 está quebrado agora.

Isso muda se você colocar o using dentro de sua declaração de namespace, da seguinte maneira:

 // File1b.cs namespace Outer.Inner { using System; class Foo { static void Bar() { double d = Math.PI; } } } 

Agora o compilador pesquisa o System antes de pesquisar Outer , localiza System.Math e está tudo bem.

Alguns argumentam que o Math pode ser um nome ruim para uma class definida pelo usuário, uma vez que já existe um no System ; O ponto aqui é que há uma diferença e isso afeta a capacidade de manutenção do seu código.

Também é interessante observar o que acontece se Foo estiver no namespace Outer , ao invés de Outer.Inner . Nesse caso, adicionar Outer.Math no File2 interrompe o File1, independentemente de onde vai o using . Isso implica que o compilador pesquisa o namespace interno mais próximo antes de examinar qualquer diretiva de using .

Este tópico já tem ótimas respostas, mas eu sinto que posso trazer um pouco mais de detalhes com essa resposta adicional.

Primeiro, lembre-se de que uma declaração de namespace com períodos, como:

 namespace MyCorp.TheProduct.SomeModule.Utilities { ... } 

é inteiramente equivalente a:

 namespace MyCorp { namespace TheProduct { namespace SomeModule { namespace Utilities { ... } } } } 

Se você quisesse, poderia using diretivas em todos esses níveis. (Claro, queremos using s em apenas um lugar, mas seria legal de acordo com a linguagem.)

A regra para resolver qual tipo está implícito, pode ser vagamente declarada assim: Primeiro pesquise o “escopo” mais interno de uma correspondência, se nada for encontrado, saia de um nível para o próximo escopo e procure lá, e assim por diante , até que uma correspondência seja encontrada. Se em algum nível mais de uma correspondência for encontrada, se um dos tipos for da assembly atual, escolha-o e emita um aviso do compilador. Caso contrário, desista (erro de tempo de compilation).

Agora, vamos ser explícitos sobre o que isso significa em um exemplo concreto com as duas principais convenções.

(1) com usos fora:

 using System; using System.Collections.Generic; using System.Linq; //using MyCorp.TheProduct; <-- uncommenting this would change nothing using MyCorp.TheProduct.OtherModule; using MyCorp.TheProduct.OtherModule.Integration; using ThirdParty; namespace MyCorp.TheProduct.SomeModule.Utilities { class C { Ambiguous a; } } 

No caso acima, para descobrir qual é o tipo Ambiguous , a pesquisa é feita nesta ordem:

  1. Tipos nesteds dentro de C (incluindo tipos nesteds herdados)
  2. Tipos no namespace atual MyCorp.TheProduct.SomeModule.Utilities
  3. Tipos no namespace MyCorp.TheProduct.SomeModule
  4. Tipos em MyCorp.TheProduct
  5. Tipos na MyCorp
  6. Tipos no namespace nulo (o namespace global)
  7. Tipos no System , System.Collections.Generic , System.Linq , MyCorp.TheProduct.OtherModule , MyCorp.TheProduct.OtherModule.Integration e ThirdParty

A outra convenção:

(2) Com utilizações no interior:

 namespace MyCorp.TheProduct.SomeModule.Utilities { using System; using System.Collections.Generic; using System.Linq; using MyCorp.TheProduct; // MyCorp can be left out; this using is NOT redundant using MyCorp.TheProduct.OtherModule; // MyCorp.TheProduct can be left out using MyCorp.TheProduct.OtherModule.Integration; // MyCorp.TheProduct can be left out using ThirdParty; class C { Ambiguous a; } } 

Agora, procure pelo tipo Ambiguous , nesta ordem:

  1. Tipos nesteds dentro de C (incluindo tipos nesteds herdados)
  2. Tipos no namespace atual MyCorp.TheProduct.SomeModule.Utilities
  3. Tipos no System , System.Collections.Generic , System.Linq , MyCorp.TheProduct , MyCorp.TheProduct.OtherModule , MyCorp.TheProduct.OtherModule.Integration e ThirdParty
  4. Tipos no namespace MyCorp.TheProduct.SomeModule
  5. Tipos na MyCorp
  6. Tipos no namespace nulo (o namespace global)

(Observe que MyCorp.TheProduct era uma parte de "3" e, portanto, não era necessário entre "4." e "5.".)

Observações conclusivas

Não importa se você coloca os itens dentro ou fora da declaração de namespace, sempre há a possibilidade de alguém adicionar um novo tipo com nome idêntico a um dos namespaces que têm maior prioridade.

Além disso, se um namespace nested tiver o mesmo nome de um tipo, isso poderá causar problemas.

É sempre perigoso mover os usos de um local para outro porque a hierarquia de pesquisa muda e outro tipo pode ser encontrado. Portanto, escolha uma convenção e atenha-se a ela, de modo que você não tenha que se movimentar.

Os modelos do Visual Studio, por padrão, colocam os usos fora do namespace (por exemplo, se você fizer o VS gerar uma nova class em um novo arquivo).

Uma (pequena) vantagem de ter usos externos é que você pode utilizar as diretivas using para um atributo global, por exemplo [assembly: ComVisible(false)] vez de [assembly: System.Runtime.InteropServices.ComVisible(false)] .

Colocá-lo dentro dos namespaces torna as declarações locais para aquele namespace para o arquivo (no caso de você ter vários namespaces no arquivo), mas se você tiver apenas um namespace por arquivo, então não faz muita diferença se eles vão para fora ou para fora. dentro do namespace.

 using ThisNamespace.IsImported.InAllNamespaces.Here; namespace Namespace1 { using ThisNamespace.IsImported.InNamespace1.AndNamespace2; namespace Namespace2 { using ThisNamespace.IsImported.InJustNamespace2; } } namespace Namespace3 { using ThisNamespace.IsImported.InJustNamespace3; } 

De acordo com Hanselman – Using Directive and Assembly Loading … e outros artigos semelhantes, tecnicamente não há diferença.

Minha preferência é colocá-los fora dos namespaces.

De acordo com a Documentação StyleCop:

SA1200: UsingDirectivesMustBePlacedWithinNamespace

Causa A diretiva AC # using é colocada fora de um elemento de namespace.

Descrição da regra Uma violação desta regra ocorre quando uma diretiva using ou uma diretiva using-alias é colocada fora de um elemento de namespace, a menos que o arquivo não contenha nenhum elemento de namespace.

Por exemplo, o código a seguir resultaria em duas violações dessa regra.

 using System; using Guid = System.Guid; namespace Microsoft.Sample { public class Program { } } 

O código a seguir, no entanto, não resultaria em violações desta regra:

 namespace Microsoft.Sample { using System; using Guid = System.Guid; public class Program { } } 

Este código irá compilar de forma limpa, sem erros de compilador. No entanto, não está claro qual versão do tipo Guid está sendo alocada. Se a diretiva using for movida dentro do namespace, como mostrado abaixo, ocorrerá um erro do compilador:

 namespace Microsoft.Sample { using Guid = System.Guid; public class Guid { public Guid(string s) { } } public class Program { public static void Main(string[] args) { Guid g = new Guid("hello"); } } } 

O código falha no seguinte erro do compilador, encontrado na linha contendo Guid g = new Guid("hello");

CS0576: O namespace ‘Microsoft.Sample’ contém uma definição conflitante com o alias ‘Guid’

O código cria um alias para o tipo System.Guid chamado Guid e também cria seu próprio tipo chamado Guid com uma interface de construtor correspondente. Mais tarde, o código cria uma instância do tipo Guid. Para criar essa instância, o compilador deve escolher entre as duas definições diferentes de Guid. Quando a diretiva using-alias é colocada fora do elemento namespace, o compilador escolherá a definição local de Guid definida dentro do namespace local e ignorará completamente a diretiva using-alias definida fora do namespace. Isso, infelizmente, não é óbvio ao ler o código.

No entanto, quando a diretiva using-alias é posicionada no espaço de nomes, o compilador deve escolher entre dois tipos diferentes e conflitantes do Guid, ambos definidos no mesmo namespace. Ambos os tipos fornecem um construtor correspondente. O compilador é incapaz de tomar uma decisão, portanto, sinaliza o erro do compilador.

Colocar a diretiva using-alias fora do namespace é uma prática ruim porque pode levar a confusão em situações como essa, onde não é óbvio qual versão do tipo está realmente sendo usada. Isso pode levar a um bug que pode ser difícil de diagnosticar.

Colocar diretivas using-alias dentro do elemento namespace elimina isso como uma fonte de bugs.

  1. Múltiplos Namespaces

Colocar vários elementos de namespace em um único arquivo geralmente é uma má idéia, mas se e quando isso for feito, é uma boa idéia colocar todas as diretivas usando dentro de cada um dos elementos de namespace, em vez de globalmente na parte superior do arquivo. Isso escopará os namespaces com firmeza e também ajudará a evitar o tipo de comportamento descrito acima.

É importante observar que quando o código foi escrito usando diretivas colocadas fora do namespace, deve-se ter cuidado ao mover essas diretivas dentro do namespace, para garantir que isso não esteja alterando a semântica do código. Como explicado acima, a colocação de diretivas using-alias dentro do elemento namespace permite que o compilador escolha entre tipos conflitantes de maneiras que não acontecerão quando as diretivas forem colocadas fora do namespace.

Como corrigir violações Para corrigir uma violação dessa regra, mova todas as diretivas usando diretivas using-alias dentro do elemento namespace.

Há um problema em colocar usando instruções dentro do namespace quando você deseja usar aliases. O alias não se beneficia das instruções anteriores e precisa ser totalmente qualificado.

Considerar:

 namespace MyNamespace { using System; using MyAlias = System.DateTime; class MyClass { } } 

versus:

 using System; namespace MyNamespace { using MyAlias = DateTime; class MyClass { } } 

Isso pode ser particularmente pronunciado se você tiver um alias longo como o seguinte (que é como eu encontrei o problema):

 using MyAlias = Tuple>, Expression>>; 

Com o using instruções dentro do namespace, de repente se torna:

 using MyAlias = System.Tuple>, System.Linq.Expressions.Expression>>; 

Feio.

Como Jeppe Stig Nielsen disse , este segmento já tem ótimas respostas, mas eu pensei que essa sutileza bastante óbvia merecia ser mencionada também.

using diretivas especificadas em namespaces pode criar códigos mais curtos, pois eles não precisam ser totalmente qualificados como quando são especificados no lado de fora.

O exemplo a seguir funciona porque os tipos Foo e Bar estão no mesmo namespace global, Outer .

Presuma o arquivo de código Foo.cs :

 namespace Outer.Inner { class Foo { } } 

E Bar.cs :

 namespace Outer { using Outer.Inner; class Bar { public Foo foo; } } 

Isso pode omitir o namespace externo na diretiva using , para abreviar:

 namespace Outer { using Inner; class Bar { public Foo foo; } } 

Outra sutileza que eu não acredito ter sido coberta pelas outras respostas é quando você tem uma class e um namespace com o mesmo nome.

Quando você tem a importação dentro do namespace, ele encontrará a class. Se a importação estiver fora do espaço de nomes, a importação será ignorada e a class e o espaço de nomes terão que ser totalmente qualificados.

 //file1.cs namespace Foo { class Foo { } } //file2.cs namespace ConsoleApp3 { using Foo; class Program { static void Main(string[] args) { //This will allow you to use the class Foo test = new Foo(); } } } //file2.cs using Foo; //Unused and redundant namespace Bar { class Bar { Bar() { Foo.Foo test = new Foo.Foo(); Foo test = new Foo(); //will give you an error that a namespace is being used like a class. } } } 

As razões técnicas são discutidas nas respostas e acho que as preferências pessoais chegam ao fim, já que a diferença não é tão grande e há desvantagens para ambas. O modelo padrão do Visual Studio para criar arquivos .cs usa diretivas fora dos namespaces, por exemplo

Pode-se ajustar o controle de estilo para verificar using diretivas fora dos namespaces através da adição do arquivo stylecop.json na raiz do arquivo de projeto com o seguinte:

 { "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json", "orderingRules": { "usingDirectivesPlacement": "outsideNamespace" } } } 

Você pode criar esse arquivo de configuração no nível da solução e adicioná-lo aos seus projetos como ‘Arquivo de link existente’ para compartilhar a configuração em todos os seus projetos também.

É uma prática melhor se esses padrões usando ” referências ” usadas na sua solução de origem devem estar fora dos namespaces e aqueles que são “novas referências adicionadas” é uma boa prática se você os colocar dentro do namespace. Isso é para distinguir quais referências estão sendo adicionadas.