¿Las directivas de "uso" deberían estar dentro o fuera del espacio de nombres en C#?
He estado ejecutando StyleCop sobre código C# y sigue informando que mis using
directivas deberían estar dentro del espacio de nombres.
¿Existe alguna razón técnica para colocar las using
directivas dentro en lugar de fuera del espacio de nombres?
En realidad, existe 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 imagina que alguien agrega otro archivo (File2.cs) al proyecto que se ve así:
// File2.cs
namespace Outer
{
class Math
{
}
}
El compilador busca Outer
antes de mirar esas using
directivas fuera del espacio de nombres, por lo que encuentra Outer.Math
en lugar de System.Math
. Desafortunadamente (¿o tal vez afortunadamente?), Outer.Math
no tiene ningún PI
miembro, por lo que File1 ahora está roto.
Esto cambia si coloca la using
declaración dentro de su 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 System
antes de buscar Outer
, encuentra System.Math
y todo está bien.
Algunos dirían que Math
podría ser un mal nombre para una clase definida por el usuario, ya que ya existe una en System
; El punto aquí es que hay una diferencia y afecta la capacidad de mantenimiento de su código.
También es interesante observar qué sucede si Foo
está en el espacio de nombres Outer
, en lugar de Outer.Inner
. En ese caso, agregar Outer.Math
el Archivo2 rompe el Archivo1 independientemente de dónde using
vaya. Esto implica que el compilador busca el espacio de nombres más interno antes de mirar cualquier using
directiva.
Este hilo ya tiene algunas respuestas excelentes, pero creo que puedo aportar un poco más de detalles con esta respuesta adicional.
Primero, recuerde que una declaración de espacio de nombres con puntos, como:
namespace MyCorp.TheProduct.SomeModule.Utilities
{
...
}
es totalmente equivalente a:
namespace MyCorp
{
namespace TheProduct
{
namespace SomeModule
{
namespace Utilities
{
...
}
}
}
}
Si quisieras, podrías poner using
directivas en todos estos niveles. (Por supuesto, queremos tener using
s 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 la siguiente manera: primero busque una coincidencia en el "alcance" más interno, si no encuentra nada allí, salga de 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 ensamblado actual, elija ese y emita una advertencia del compilador. De lo contrario, abandone (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 exteriores:
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 saber de qué tipo Ambiguous
es, la búsqueda va en este orden:
- Tipos anidados internos
C
(incluidos los tipos anidados heredados) - Tipos en el espacio de nombres actual
MyCorp.TheProduct.SomeModule.Utilities
- Tipos en el espacio de nombres
MyCorp.TheProduct.SomeModule
- tipos en
MyCorp.TheProduct
- tipos en
MyCorp
- Tipos en el espacio de nombres nulo (el espacio de nombres global)
- Escribe
System
,System.Collections.Generic
,System.Linq
,MyCorp.TheProduct.OtherModule
,MyCorp.TheProduct.OtherModule.Integration
yThirdParty
La otra convención:
(2) Con usos en el 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;
}
}
Ahora, busque el tipo Ambiguous
en este orden:
- Tipos anidados internos
C
(incluidos los tipos anidados heredados) - Tipos en el espacio de nombres actual
MyCorp.TheProduct.SomeModule.Utilities
- Escribe
System
,System.Collections.Generic
,System.Linq
,MyCorp.TheProduct
,MyCorp.TheProduct.OtherModule
,MyCorp.TheProduct.OtherModule.Integration
yThirdParty
- Tipos en el espacio de nombres
MyCorp.TheProduct.SomeModule
- tipos en
MyCorp
- Tipos en el espacio de nombres nulo (el espacio de nombres global)
(Tenga en cuenta que MyCorp.TheProduct
era 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 agregue más adelante un nuevo tipo con nombre idéntico a uno de los espacios de nombres que tiene 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 es posible que se encuentre otro tipo. Por lo tanto, elija una convención y cúmplala, de modo que nunca tenga que cambiar de uso.
Las plantillas de Visual Studio, de forma predeterminada, colocan los usos fuera del espacio de nombres (por ejemplo, si hace que VS genere una nueva clase en un archivo nuevo).
Una (pequeña) ventaja de tener usos externos es que luego puedes utilizar las directivas de uso para un atributo global, por ejemplo, [assembly: ComVisible(false)]
en lugar de [assembly: System.Runtime.InteropServices.ComVisible(false)]
.
Actualización sobre declaraciones de espacios de nombres con ámbito de archivo
Desde C# 10.0 (a partir de 2021), puedes evitar la sangría y usar cualquiera de las dos (convención 1, usos externos):
using System;
using System.Collections.Generic;
using System.Linq;
using MyCorp.TheProduct.OtherModule;
using MyCorp.TheProduct.OtherModule.Integration;
using ThirdParty;
namespace MyCorp.TheProduct.SomeModule.Utilities;
class C
{
Ambiguous a;
}
o (convención 2, usos internos):
namespace MyCorp.TheProduct.SomeModule.Utilities;
using System;
using System.Collections.Generic;
using System.Linq;
using MyCorp.TheProduct;
using MyCorp.TheProduct.OtherModule;
using MyCorp.TheProduct.OtherModule.Integration;
using ThirdParty;
class C
{
Ambiguous a;
}
Pero se aplican las mismas consideraciones que antes.
Ponerlo dentro de los espacios de nombres hace que las declaraciones sean locales para ese espacio de nombres del archivo (en caso de que tenga varios espacios de nombres en el archivo), pero si solo tiene un espacio de nombres por archivo, entonces no hay mucha diferencia si salen o 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;
}
Según Hanselman - Directiva de uso y carga de ensamblaje... y otros artículos similares, técnicamente no hay diferencia.
Mi preferencia es ponerlos fuera de los espacios de nombres.