¿Deberían las directivas 'usar' estar dentro o fuera del espacio de nombres?

2062

He estado ejecutando StyleCop sobre algún código C #, y sigue informando que mis usingdirectivas deben estar dentro del espacio de nombres.

¿Hay alguna razón técnica para colocar las usingdirectivas dentro en lugar de fuera del espacio de nombres?

benPearce
fuente
44
A veces hace la diferencia donde pones los usos: stackoverflow.com/questions/292535/linq-to-sql-designer-bug
gius
82
Solo como referencia, hay implicaciones más allá de la cuestión de múltiples clases por archivo, así que si eres nuevo en esta pregunta, sigue leyendo.
Charlie
3
@ usuario-12506: esto no funciona muy bien en un equipo de desarrollo medio a grande donde se requiere cierto nivel de coherencia de código. Y como se señaló anteriormente, si no comprende los diferentes diseños, puede encontrar casos extremos que no funcionan como esperaba.
benPearce
35
Terminología: esas no son using declaraciones ; Son using directivas . Una usingdeclaración, por otro lado, es una estructura de lenguaje que ocurre junto con otras declaraciones dentro del cuerpo de un método, etc. Como ejemplo, using (var e = s.GetEnumerator()) { /* ... */ }es una declaración que es más o menos igual que var e = s.GetEnumerator(); try { /* ... */ } finally { if (e != null) { e.Dispose(); } }.
Jeppe Stig Nielsen
1
Si esto ya no fue mencionado por nadie, en realidad Microsoft también recomienda poner usingdeclaraciones dentro de las namespacedeclaraciones, en sus
directrices de

Respuestas:

2133

En realidad, hay una diferencia (sutil) entre los dos. Imagine que tiene el siguiente código en File1.cs:

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

Ahora imagine que alguien agrega otro archivo (File2.cs) al proyecto que se ve así:

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

El compilador busca Outerantes de mirar esas usingdirectivas fuera del espacio de nombres, por lo que encuentra en Outer.Mathlugar de System.Math. Desafortunadamente (¿o quizás afortunadamente?), No Outer.Mathtiene ningún PImiembro, por lo que File1 ahora está roto.

Esto cambia si coloca usingdentro de su declaración de espacio de nombres, de la siguiente manera:

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

Ahora el compilador busca Systemantes de buscar Outer, encuentra System.Mathy todo está bien.

Algunos argumentarían que Mathpodría ser un mal nombre para una clase definida por el usuario, ya que ya hay una System; El punto aquí es que no es una diferencia, y que afecta a la capacidad de mantenimiento de su código.

También es interesante observar lo que sucede si Fooestá en el espacio de nombres Outer, en lugar de hacerlo Outer.Inner. En ese caso, agregar Outer.MathFile2 rompe File1 independientemente de dónde usingvaya. Esto implica que el compilador busca el espacio de nombres que lo encierra más interno antes de mirar cualquier usingdirectiva.

Charlie
fuente
28
Esta es, en mi opinión, una razón mucho mejor para usar declaraciones locales que el argumento de múltiples espacios de nombres en un archivo de Mark. Especialmente, la compilación puede quejarse y se quejará del choque de nombres (consulte la documentación de StyleCop para esta regla (por ejemplo, según lo publicado por Jared)).
David Schmitt
148
La respuesta aceptada es buena, pero para mí parece una buena razón para colocar las cláusulas de uso fuera del espacio de nombres. Si estoy en el espacio de nombres Outer.Inner, esperaría que use la clase Math de Outer.Inner y no System.Math.
Frank Wallis
77
Estoy de acuerdo con esto también. La respuesta aceptada es correcta porque técnicamente describe la diferencia. Sin embargo, una u otra clase necesitará una llamada explícita. Me gustaría mucho que "Math" resolviera mi propia clase local, y "System.Math" se refiriera a la clase externa, incluso si System.Math se usara como "Math" antes de que Outer.Math existiera. Sí, es más trabajo arreglar muchas referencias preexistentes, pero eso también podría ser una pista de que quizás Outer.Math debería tener un nombre diferente.
mbmcavoy
13
Gran respuesta, pero me parece que solo quisiera poner localmente no-framework usando declaraciones localmente, y mantener el framework usando declaraciones globales. ¿Alguien tiene más explicaciones por qué debería cambiar completamente mi preferencia? Además, ¿de dónde vino esto, las plantillas en VS2008 ponen fuera del espacio de nombres?
Thymine
31
Creo que esta es más una convención de nombres errónea en lugar de cambiar el lugar de su uso. No debería haber una clase llamada Math en su solución
jDeveloper el
455

Este hilo ya tiene algunas respuestas geniales, pero creo que puedo aportar un poco más de detalle con esta respuesta adicional.

Primero, recuerde que una declaración de espacio de nombres con puntos, como:

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

es completamente equivalente a:

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

Si quisieras, podrías poner usingdirectivas en todos estos niveles. (Por supuesto, queremos tener usings en un solo lugar, pero sería legal según el idioma).

La regla para resolver qué tipo está implícito, se puede expresar de manera general de esta manera: primero busque una "coincidencia" más interna para una coincidencia, si no se encuentra nada, salga un nivel al siguiente alcance y busque allí, y así sucesivamente , hasta que se encuentre una coincidencia. Si en algún nivel se encuentra más de una coincidencia, si uno de los tipos es del ensamblaje actual, elíjalo y emita una advertencia de compilación. De lo contrario, renunciar (error en tiempo de compilación).

Ahora, seamos explícitos sobre lo que esto significa en un ejemplo concreto con las dos convenciones principales.

(1) Con usos fuera:

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;
    }
}

En el caso anterior, para averiguar qué tipo Ambiguouses, la búsqueda va en este orden:

  1. Tipos anidados en el interior C(incluidos los tipos anidados heredados)
  2. Tipos en el espacio de nombres actual MyCorp.TheProduct.SomeModule.Utilities
  3. Tipos en el espacio de nombres MyCorp.TheProduct.SomeModule
  4. Tipos en MyCorp.TheProduct
  5. Tipos en MyCorp
  6. Tipos en el espacio de nombres nulo (el espacio de nombres global)
  7. Tipos de System, System.Collections.Generic, System.Linq, MyCorp.TheProduct.OtherModule, MyCorp.TheProduct.OtherModule.Integration, yThirdParty

La otra convención:

(2) Con usos dentro:

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;
    }
}

Ahora, busque el tipo Ambiguousen este orden:

  1. Tipos anidados en el interior C(incluidos los tipos anidados heredados)
  2. Tipos en el espacio de nombres actual MyCorp.TheProduct.SomeModule.Utilities
  3. Tipos de System, System.Collections.Generic, System.Linq, MyCorp.TheProduct, MyCorp.TheProduct.OtherModule, MyCorp.TheProduct.OtherModule.Integration, yThirdParty
  4. Tipos en el espacio de nombres MyCorp.TheProduct.SomeModule
  5. Tipos en MyCorp
  6. Tipos en el espacio de nombres nulo (el espacio de nombres global)

(Tenga en cuenta que MyCorp.TheProductera parte de "3." y, por lo tanto, no era necesario entre "4." y "5.").

Observaciones finales

No importa si coloca los usos dentro o fuera de la declaración del espacio de nombres, siempre existe la posibilidad de que alguien luego agregue un nuevo tipo con un nombre idéntico a uno de los espacios de nombres que tienen mayor prioridad.

Además, si un espacio de nombres anidado tiene el mismo nombre que un tipo, puede causar problemas.

Siempre es peligroso mover los usos de una ubicación a otra porque la jerarquía de búsqueda cambia y se puede encontrar otro tipo. Por lo tanto, elija una convención y cúmplala, para que nunca tenga que mover los usos.

Las plantillas de Visual Studio, por defecto, colocan los usos fuera del espacio de nombres (por ejemplo, si hace que VS genere una nueva clase en un nuevo archivo).

Una (pequeña) ventaja de tener usos externos es que puede utilizar las directivas de uso para un atributo global, por ejemplo, en [assembly: ComVisible(false)]lugar de [assembly: System.Runtime.InteropServices.ComVisible(false)].

Jeppe Stig Nielsen
fuente
46
Esta es la mejor explicación, porque resalta el hecho de que la posición de las declaraciones de "uso" es una decisión deliberada del desarrollador. En ningún caso alguien debería cambiar descuidadamente la ubicación de las declaraciones de "uso" sin comprender las implicaciones. Por lo tanto, la regla StyleCop es simplemente tonta.
ZunTzu
194

Ponerlo dentro de los espacios de nombres hace que las declaraciones sean locales a ese espacio de nombres para el archivo (en caso de que tenga múltiples espacios de nombres en el archivo), pero si solo tiene un espacio de nombres por archivo, no hay mucha diferencia si salen o no dentro del espacio de nombres.

using ThisNamespace.IsImported.InAllNamespaces.Here;

namespace Namespace1
{ 
   using ThisNamespace.IsImported.InNamespace1.AndNamespace2;

   namespace Namespace2
   { 
      using ThisNamespace.IsImported.InJustNamespace2;
   }       
}

namespace Namespace3
{ 
   using ThisNamespace.IsImported.InJustNamespace3;
}
Mark Cidade
fuente
los espacios de nombres proporcionan una separación lógica, no física (archivo).
Jowen
99
No es del todo cierto que no haya diferencia; usingLas directivas dentro de los namespacebloques pueden referirse a espacios de nombres relativos basados ​​en el namespacebloque adjunto .
O Mapper
70
si lo se. establecimos que en la respuesta aceptada de esta pregunta hace cinco años.
Mark Cidade
59

De acuerdo con Hanselman - Uso de la directiva y la carga de ensamblajes ... y otros artículos similares técnicamente no hay diferencia.

Mi preferencia es ponerlos fuera de los espacios de nombres.

Quintin Robinson
fuente
3
@ Chris M: eh ... el enlace publicado en la respuesta indica que hay ningún beneficio en comparación a cabo, en realidad muestra un ejemplo de que falsifica la afirmación hecha en el enlace informados ...
johnny
2
Sí, no leí completamente el hilo, pero lo compré cuando los MVP dijeron que era correcto. Un tipo lo refuta, lo explica y muestra su código más abajo ... "La IL que genera el compilador de C # es la misma en ambos casos. De hecho, el compilador de C # no genera exactamente nada que corresponda a cada directiva que usa. Las directivas que usan son puramente un C # ism, y no tienen ningún significado para .NET en sí. (No es cierto para el uso de declaraciones, pero son algo bastante diferente.) " Groups.google.com/group/wpf-disciples/msg/781738deb0a15c46
Chris McKee
84
Por favor incluya un resumen del enlace. Cuando se rompe el vínculo (ya que será suceder, dado el tiempo suficiente), de repente una respuesta con 32 upvotes es sólo vale la pena My style is to put them outside the namespaces.- apenas una respuesta en absoluto.
ANeves
11
La afirmación aquí es simplemente incorrecta ... hay una diferencia técnica y su propia cita lo dice ... de hecho, de eso se trata. Elimine esta respuesta errónea ... hay muchas mejores y precisas.
Jim Balter
53

Según la documentación de StyleCop:

SA1200: UsingDirectivesMustBePlacedWithinNamespace

Causa AC # usando directiva se coloca fuera de un elemento de espacio de nombres.

Descripción de la regla Se produce una violación de esta regla cuando una directiva using o una directiva using-alias se coloca fuera de un elemento de espacio de nombres, a menos que el archivo no contenga ningún elemento de espacio de nombres.

Por ejemplo, el siguiente código daría lugar a dos violaciones de esta regla.

using System;
using Guid = System.Guid;

namespace Microsoft.Sample
{
    public class Program
    {
    }
}

Sin embargo, el siguiente código no daría lugar a ninguna violación de esta regla:

namespace Microsoft.Sample
{
    using System;
    using Guid = System.Guid;

    public class Program
    {
    }
}

Este código se compilará limpiamente, sin errores de compilación. Sin embargo, no está claro qué versión del tipo Guid se está asignando. Si la directiva using se mueve dentro del espacio de nombres, como se muestra a continuación, se producirá un error del 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");
        }
    }
}

El código falla en el siguiente error del compilador, que se encuentra en la línea que contiene Guid g = new Guid("hello");

CS0576: El espacio de nombres 'Microsoft.Sample' contiene una definición en conflicto con el alias 'Guid'

El código crea un alias para el tipo System.Guid llamado Guid, y también crea su propio tipo llamado Guid con una interfaz de constructor coincidente. Más tarde, el código crea una instancia del tipo Guid. Para crear esta instancia, el compilador debe elegir entre las dos definiciones diferentes de Guid. Cuando la directiva using-alias se coloca fuera del elemento de espacio de nombres, el compilador elegirá la definición local de Guid definida dentro del espacio de nombres local, e ignorará por completo la directiva using-alias definida fuera del espacio de nombres. Esto, desafortunadamente, no es obvio al leer el código.

Sin embargo, cuando la directiva using-alias se coloca dentro del espacio de nombres, el compilador debe elegir entre dos tipos Guid diferentes y conflictivos, ambos definidos dentro del mismo espacio de nombres. Ambos tipos proporcionan un constructor coincidente. El compilador no puede tomar una decisión, por lo que marca el error del compilador.

Colocar la directiva using-alias fuera del espacio de nombres es una mala práctica porque puede generar confusión en situaciones como esta, donde no es obvio qué versión del tipo se está utilizando realmente. Potencialmente, esto puede conducir a un error que puede ser difícil de diagnosticar.

Colocar directivas con alias dentro del elemento de espacio de nombres elimina esto como fuente de errores.

  1. Múltiples espacios de nombres

Colocar múltiples elementos de espacio de nombres dentro de un solo archivo generalmente es una mala idea, pero si esto se hace, es una buena idea colocar todos los directorios que usan dentro de cada uno de los elementos de espacio de nombres, en lugar de globalmente en la parte superior del archivo. Esto abarcará estrechamente los espacios de nombres y también ayudará a evitar el tipo de comportamiento descrito anteriormente.

Es importante tener en cuenta que cuando el código se ha escrito utilizando directivas colocadas fuera del espacio de nombres, se debe tener cuidado al mover estas directivas dentro del espacio de nombres, para asegurarse de que esto no cambie la semántica del código. Como se explicó anteriormente, la colocación de directivas using-alias dentro del elemento de espacio de nombres permite al compilador elegir entre tipos en conflicto de formas que no sucederán cuando las directivas se coloquen fuera del espacio de nombres.

Cómo corregir infracciones Para corregir una infracción de esta regla, mueva todas las directivas y directivas con alias dentro del elemento de espacio de nombres.

JaredCacurak
fuente
1
@Jared: como señalé en mi respuesta, mi solución / solución preferida es tener solo una clase por archivo. Creo que esta es una convención bastante común.
benPearce
24
De hecho, ¡también es una regla de StyleCop! SA1402: el documento AC # solo puede contener una sola clase en el nivel raíz, a menos que todas las clases sean parciales y sean del mismo tipo. Mostrar una regla rompiendo otra simplemente gotea con salsa incorrecta.
Tarea
66
Votado por ser la primera respuesta para cubrirlo desde la perspectiva de StyleCop. Personalmente, me gusta la sensación visual de usings fuera del espacio de nombres. El interior me usingparece tan feo. :)
nawfal
2
Finalmente una buena respuesta a la pregunta. Y el comentario de benPearce es irrelevante ... esto no tiene nada que ver con la cantidad de clases en el archivo.
Jim Balter
35

Existe un problema con la colocación de declaraciones de uso dentro del espacio de nombres cuando desea utilizar alias. El alias no se beneficia de las usingdeclaraciones anteriores y tiene que estar completamente calificado.

Considerar:

namespace MyNamespace
{
    using System;
    using MyAlias = System.DateTime;

    class MyClass
    {
    }
}

versus:

using System;

namespace MyNamespace
{
    using MyAlias = DateTime;

    class MyClass
    {
    }
}

Esto puede ser particularmente pronunciado si tiene un alias de largo aliento como el siguiente (que es cómo encontré el problema):

using MyAlias = Tuple<Expression<Func<DateTime, object>>, Expression<Func<TimeSpan, object>>>;

Con usingdeclaraciones dentro del espacio de nombres, de repente se convierte en:

using MyAlias = System.Tuple<System.Linq.Expressions.Expression<System.Func<System.DateTime, object>>, System.Linq.Expressions.Expression<System.Func<System.TimeSpan, object>>>;

No es bonito.

Neo
fuente
1
Su classnecesita un nombre (identificador). No puede tener una usingdirectiva dentro de una clase como lo indica. Debe estar en un nivel de espacio de nombres, por ejemplo fuera del más externo namespace, o solo dentro del más interno namespace(pero no dentro de una clase / interfaz / etc.).
Jeppe Stig Nielsen
@JeppeStigNielsen Gracias. Perdí las usingdirectivas erróneamente. Lo he editado como pretendía que fuera. Gracias por señalarlo. Sin embargo, el razonamiento sigue siendo el mismo.
Neo
4

Como dijo Jeppe Stig Nielsen , este hilo ya tiene excelentes respuestas, pero también pensé que valía la pena mencionar esta sutileza bastante obvia.

using Las directivas especificadas dentro de los espacios de nombres pueden hacer que el código sea más corto ya que no necesitan estar completamente calificadas como cuando se especifican en el exterior.

El siguiente ejemplo funciona porque los tipos Fooy Barson a la vez en el mismo espacio de nombres global, Outer.

Presume el archivo de código Foo.cs :

namespace Outer.Inner
{
    class Foo { }
}

Y Bar.cs :

namespace Outer
{
    using Outer.Inner;

    class Bar
    {
        public Foo foo;
    }
}

Eso puede omitir el espacio de nombres externo en la usingdirectiva, para abreviar:

namespace Outer
{
    using Inner;

    class Bar
    {
        public Foo foo;
    }
}
Galletas
fuente
8
Es cierto que "puede omitir el espacio de nombres externo", pero eso no significa que deba hacerlo. Para mí, este es otro argumento de por qué el uso de directivas (aparte de los alias como en la respuesta de @ Neo) debería ir fuera del espacio de nombres, para forzar nombres de espacios de nombres completamente calificados.
Keith Robertson el
4

Me encontré con una arruga (que no está cubierta en otras respuestas):

Supongamos que tiene estos espacios de nombres:

  • Algún otro
  • Padre, algo, otro

Cuando se usa using Something.Other fuera de a namespace Parent, se refiere al primero (Something.Other).

Sin embargo, si lo usa dentro de esa declaración de espacio de nombres, ¡se refiere a la segunda (Parent.Something.Other)!

Hay una solución simple: agregue el global::prefijo " ": docs

namespace Parent
{
   using global::Something.Other;
   // etc
}
Hans Kein
fuente
2

Las razones técnicas se discuten en las respuestas y creo que al final se trata de las preferencias personales ya que la diferencia no es tan grande y hay compensaciones para ambos. La plantilla predeterminada de Visual Studio para crear .csarchivos usa usingdirectivas fuera de los espacios de nombres, por ejemplo

Se puede ajustar stylecop para verificar usingdirectivas fuera de los espacios de nombres agregando un stylecop.jsonarchivo en la raíz del archivo del proyecto con lo siguiente:

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

Puede crear este archivo de configuración en el nivel de solución y agregarlo a sus proyectos como 'Archivo de enlace existente' para compartir la configuración en todos sus proyectos también.

sotn
fuente
2

Otra sutileza que no creo que haya sido cubierta por las otras respuestas es cuando tienes una clase y un espacio de nombres con el mismo nombre.

Cuando tenga la importación dentro del espacio de nombres, encontrará la clase. Si la importación está fuera del espacio de nombres, la importación se ignorará y la clase y el espacio de nombres deberán estar completamente calificados.

//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.
        }
    }
}
Ben Gardner
fuente
-8

Es una mejor práctica si los que usan por defecto, es decir, las " referencias " utilizadas en su solución de origen deben estar fuera de los espacios de nombres y aquellos que son "nueva referencia agregada" es una buena práctica si debe colocarla dentro del espacio de nombres. Esto es para distinguir qué referencias se están agregando.

Israel Ocbina
fuente
66
No, en realidad es una mala idea. No debe basar la ubicación entre el ámbito local y el ámbito global del uso de directivas en el hecho de que se hayan agregado recientemente o no. En cambio, es una buena práctica ordenarlos alfabéticamente, a excepción de las referencias BCL, que deberían ir arriba.
Abel