Le direttive "using" dovrebbero essere all'interno o all'esterno dello spazio dei nomi?


2062

Ho eseguito StyleCop su un codice C # e continua a segnalare che le mie usingdirettive dovrebbero essere all'interno dello spazio dei nomi.

C'è un motivo tecnico per inserire le usingdirettive anziché all'esterno dello spazio dei nomi?


4
A volte si fa differenza dove si mettono usings: stackoverflow.com/questions/292535/linq-to-sql-designer-bug
Gius

82
Solo per riferimento, ci sono implicazioni oltre la semplice domanda di più classi per file, quindi se sei nuovo a questa domanda, continua a leggere.
Charlie,

3
@ user-12506 - questo non funziona molto bene in un team di sviluppo medio-grande dove è richiesto un certo livello di coerenza del codice. E come notato in precedenza, se non capisci i diversi layout potresti trovare casi limite che non funzionano come previsto.
benPearce,

35
Terminologia: quelle non sono using dichiarazioni ; sono using direttive . Una usingdichiarazione, d'altra parte, è una struttura linguistica che si verifica insieme ad altre istruzioni all'interno di un metodo corpo ecc Come un esempio, using (var e = s.GetEnumerator()) { /* ... */ }è una dichiarazione che è liberamente lo stesso var e = s.GetEnumerator(); try { /* ... */ } finally { if (e != null) { e.Dispose(); } }.
Jeppe Stig Nielsen,

1
Se questo non è già stato menzionato da nessuno, in realtà anche Microsoft consiglia di inserire le usingdichiarazioni all'interno delle namespacedichiarazioni, nelle loro linee guida per la codifica interna
user1451111

Risposte:


2133

C'è in realtà una (sottile) differenza tra i due. Immagina di avere il seguente codice in File1.cs:

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

Ora immagina che qualcuno aggiunga un altro file (File2.cs) al progetto che assomigli a questo:

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

Il compilatore cerca Outerprima di guardare quelle usingdirettive al di fuori dello spazio dei nomi, quindi trova Outer.Mathinvece di System.Math. Sfortunatamente (o forse per fortuna?), Outer.MathNon ha PImembri, quindi File1 è ora rotto.

Questo cambia se si inserisce usingall'interno della dichiarazione dello spazio dei nomi, come segue:

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

Ora il compilatore cerca Systemprima di cercare Outer, trova System.Mathe tutto va bene.

Alcuni sosterrebbero che Mathpotrebbe essere un brutto nome per una classe definita dall'utente, poiché ce n'è già una in System; il punto qui è solo che ci sia una differenza, e colpisce la manutenibilità del codice.

È anche interessante notare cosa succede se si Footrova nello spazio dei nomi Outer, piuttosto che Outer.Inner. In tal caso, l'aggiunta Outer.Mathdi File2 interrompe File1 indipendentemente da dove usingva. Ciò implica che il compilatore cerca nello spazio dei nomi che racchiude più interno prima di esaminare qualsiasi usingdirettiva.


28
Questo è un motivo molto migliore per inserire le istruzioni localmente rispetto all'argomento più spazi dei nomi in un file di Mark. In particolar modo la compilazione può e si lamenterà dello scontro sui nomi (vedere la documentazione di StyleCop per questa regola (ad esempio, come pubblicata da Jared)).
David Schmitt,

148
La risposta accettata è buona, ma a me sembra una buona ragione per mettere le clausole using fuori dallo spazio dei nomi. Se mi trovo nello spazio dei nomi Outer.Inner, mi aspetto che utilizzi la classe Math da Outer.Inner e non System.Math.
Frank Wallis,

7
Sono d'accordo anche con questo. La risposta accettata è corretta in quanto descrive tecnicamente la differenza. Tuttavia, l'una o l'altra classe avrà bisogno di un callout esplicito. Mi piacerebbe moltissimo che "Math" si risolvesse nella mia classe locale e "System.Math" si riferisse alla classe esterna, anche se System.Math veniva usato come "Math" prima dell'esistenza di Outer.Math. Sì, è più lavoro per correggere comunque molti riferimenti preesistenti, ma potrebbe anche essere un suggerimento che forse Outer.Math dovrebbe avere un nome diverso!
mbmcavoy,

13
Ottima risposta, ma mi sembra che vorrei solo mettere le istruzioni non-framework usando localmente e mantenere il framework usando dichiarazioni globali. Qualcuno ha ulteriori spiegazioni sul perché dovrei cambiare completamente le mie preferenze? Inoltre, da dove viene questo, i template di VS2008 sono stati usati al di fuori dello spazio dei nomi?
Thymine,

31
Penso che questa sia più una cattiva convenzione di denominazione piuttosto che cambiare il luogo del tuo utilizzo. Non ci dovrebbe essere una classe chiamata Math nella tua soluzione
jDeveloper,

455

Questa discussione ha già delle ottime risposte, ma credo di poter aggiungere un po 'più di dettagli con questa risposta aggiuntiva.

Innanzitutto, ricorda che una dichiarazione dello spazio dei nomi con punti, come:

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

è del tutto equivalente a:

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

Se lo volessi, potresti mettere le usingdirettive su tutti questi livelli. (Certo, vogliamo avere usings in un solo posto, ma sarebbe legale in base alla lingua.)

La regola per risolvere quale tipo è implicito, può essere liberamente definita in questo modo: per prima cosa cerca la "portata" più interna per una corrispondenza, se non viene trovato nulla esci da un livello all'ambito successivo e cerca lì, e così via , fino a quando non viene trovata una corrispondenza. Se a un certo livello viene rilevata più di una corrispondenza, se uno dei tipi proviene dall'assembly corrente, selezionarlo ed emettere un avviso del compilatore. Altrimenti, rinuncia (errore di compilazione).

Ora, siamo espliciti su cosa significhi in un esempio concreto con le due principali convenzioni.

(1) Con usi all'esterno:

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

Nel caso precedente, per scoprire di che tipo Ambiguousè, la ricerca procede in questo ordine:

  1. Tipi nidificati all'interno C(compresi i tipi nidificati ereditati)
  2. Digita nello spazio dei nomi corrente MyCorp.TheProduct.SomeModule.Utilities
  3. Tipi nello spazio dei nomi MyCorp.TheProduct.SomeModule
  4. Tipi in MyCorp.TheProduct
  5. Tipi in MyCorp
  6. Tipi nello spazio dei nomi null (lo spazio dei nomi globale)
  7. Tipi in System, System.Collections.Generic, System.Linq, MyCorp.TheProduct.OtherModule, MyCorp.TheProduct.OtherModule.Integration, eThirdParty

L'altra convenzione:

(2) Con usi all'interno:

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

Ora, cerca il tipo Ambiguousva in questo ordine:

  1. Tipi nidificati all'interno C(compresi i tipi nidificati ereditati)
  2. Digita nello spazio dei nomi corrente MyCorp.TheProduct.SomeModule.Utilities
  3. Tipi in System, System.Collections.Generic, System.Linq, MyCorp.TheProduct, MyCorp.TheProduct.OtherModule, MyCorp.TheProduct.OtherModule.Integration, eThirdParty
  4. Tipi nello spazio dei nomi MyCorp.TheProduct.SomeModule
  5. Tipi in MyCorp
  6. Tipi nello spazio dei nomi null (lo spazio dei nomi globale)

(Si noti che MyCorp.TheProductfaceva parte di "3." e pertanto non era necessario tra "4." e "5.".)

Osservazioni conclusive

Non importa se si inseriscono gli usi all'interno o all'esterno della dichiarazione dello spazio dei nomi, c'è sempre la possibilità che qualcuno in seguito aggiunga un nuovo tipo con lo stesso nome a uno degli spazi dei nomi che hanno una priorità più alta.

Inoltre, se uno spazio dei nomi nidificato ha lo stesso nome di un tipo, può causare problemi.

È sempre pericoloso spostare gli utilizzi da una posizione all'altra perché la gerarchia di ricerca cambia e può essere trovato un altro tipo. Pertanto, scegliere una convenzione e attenersi ad essa, in modo da non dover mai spostare le usanze.

I modelli di Visual Studio, per impostazione predefinita, mettono gli usi al di fuori dello spazio dei nomi (ad esempio se si fa in modo che VS generi una nuova classe in un nuovo file).

Uno (piccolo) vantaggio di avere usi esterni è che puoi quindi utilizzare le direttive di utilizzo per un attributo globale, ad esempio [assembly: ComVisible(false)]invece di [assembly: System.Runtime.InteropServices.ComVisible(false)].


46
Questa è la migliore spiegazione, perché evidenzia il fatto che la posizione delle istruzioni "using" è una decisione deliberata da parte dello sviluppatore. In nessun caso qualcuno dovrebbe cambiare incurantemente la posizione delle istruzioni "using" senza comprenderne le implicazioni. Pertanto la regola StyleCop è semplicemente stupida.
Zun Tzu,

194

Inserendolo negli spazi dei nomi rende le dichiarazioni locali allo spazio dei nomi per il file (nel caso in cui tu abbia più spazi dei nomi nel file) ma se hai solo uno spazio dei nomi per file, allora non fa molta differenza se vanno fuori o all'interno dello spazio dei nomi.

using ThisNamespace.IsImported.InAllNamespaces.Here;

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

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

namespace Namespace3
{ 
   using ThisNamespace.IsImported.InJustNamespace3;
}

gli spazi dei nomi forniscono una separazione logica, non fisica (file).
Jowen,

9
Non è del tutto vero che non c'è differenza; usingle direttive all'interno dei namespaceblocchi possono fare riferimento a spazi dei nomi relativi in ​​base al namespaceblocco accluso .
OR Mapper

70
si lo so. abbiamo stabilito che nella risposta accettata da questa domanda cinque anni fa.
Mark Cidade,

59

Secondo Hanselman - Uso della direttiva e caricamento in assemblea ... e altri articoli simili non c'è tecnicamente alcuna differenza.

La mia preferenza è di metterli al di fuori degli spazi dei nomi.


3
@Chris M: uh ... il link pubblicato nella risposta indica che non c'è alcun vantaggio in in / out, in realtà mostra un esempio che falsifica il reclamo fatto nel link che hai pubblicato ...
johnny

2
Sì, non ho letto completamente il thread, ma ho comprato quando gli MVP hanno detto che era giusto. Un ragazzo lo smentisce, lo spiega e mostra il suo codice più in basso ... "L'IL che il compilatore C # genera è lo stesso in entrambi i casi. In realtà il compilatore C # non genera esattamente nulla corrispondente a ciascuna direttiva usando. L'uso delle direttive è puramente un C # ism, e non hanno alcun significato per .NET stesso. (Non è vero per l'utilizzo di istruzioni ma sono qualcosa di abbastanza diverso.) " Groups.google.com/group/wpf-disciples/msg/781738deb0a15c46
Chris McKee

84
Si prega di includere un riepilogo del collegamento. Quando il collegamento è rotto (perché sarà accadere, dato abbastanza tempo), improvvisamente una risposta con 32 upvotes è solo la pena My style is to put them outside the namespaces.- a malapena una risposta a tutti.
ANeves,

11
L'affermazione qui è semplicemente sbagliata ... c'è una differenza tecnica e la tua stessa citazione lo dice ... in effetti, questo è tutto. Elimina questa risposta errata ... ce ne sono di molto migliori e accurate.
Jim Balter,

53

Secondo la documentazione StyleCop:

SA1200: UsingDirectivesMustBePlacedWithinNamespace

Causa AC # utilizzando la direttiva viene posizionato all'esterno di un elemento dello spazio dei nomi.

Descrizione della regola Una violazione di questa regola si verifica quando una direttiva using o una using-alias viene posizionata all'esterno di un elemento dello spazio dei nomi, a meno che il file non contenga elementi dello spazio dei nomi.

Ad esempio, il codice seguente comporterebbe due violazioni di questa regola.

using System;
using Guid = System.Guid;

namespace Microsoft.Sample
{
    public class Program
    {
    }
}

Il codice seguente, tuttavia, non comporterebbe alcuna violazione di questa regola:

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

    public class Program
    {
    }
}

Questo codice verrà compilato in modo pulito, senza errori di compilazione. Tuttavia, non è chiaro quale versione del tipo Guid venga allocata. Se la direttiva using viene spostata all'interno dello spazio dei nomi, come mostrato di seguito, si verificherà un errore del compilatore:

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

Il codice non riesce al seguente errore del compilatore, trovato nella riga contenente Guid g = new Guid("hello");

CS0576: Lo spazio dei nomi "Microsoft.Sample" contiene una definizione in conflitto con l'alias "Guid"

Il codice crea un alias per il tipo System.Guid chiamato Guid e crea anche un proprio tipo chiamato Guid con un'interfaccia di costruzione corrispondente. Successivamente, il codice crea un'istanza del tipo Guid. Per creare questa istanza, il compilatore deve scegliere tra le due diverse definizioni di Guid. Quando la direttiva using-alias viene posizionata all'esterno dell'elemento namespace, il compilatore sceglierà la definizione locale di Guid definita all'interno dello spazio dei nomi locale e ignorerà completamente la direttiva using-alias definita al di fuori dello spazio dei nomi. Questo, sfortunatamente, non è ovvio quando si legge il codice.

Quando la direttiva using-alias è posizionata all'interno dello spazio dei nomi, tuttavia, il compilatore deve scegliere tra due diversi tipi Guid, in conflitto, entrambi definiti nello stesso spazio dei nomi. Entrambi questi tipi forniscono un costruttore corrispondente. Il compilatore non è in grado di prendere una decisione, quindi contrassegna l'errore del compilatore.

Posizionare la direttiva using-alias al di fuori dello spazio dei nomi è una cattiva pratica perché può creare confusione in situazioni come questa, dove non è ovvio quale versione del tipo sia effettivamente utilizzata. Questo può potenzialmente portare a un bug che potrebbe essere difficile da diagnosticare.

Il posizionamento di direttive using-alias all'interno dell'elemento namespace elimina ciò come fonte di bug.

  1. Più spazi dei nomi

Posizionare più elementi dello spazio dei nomi all'interno di un singolo file è generalmente una cattiva idea, ma se e quando ciò è fatto, è una buona idea posizionare tutti usando le direttive all'interno di ciascuno degli elementi dello spazio dei nomi, piuttosto che globalmente nella parte superiore del file. Ciò consentirà di individuare strettamente gli spazi dei nomi e aiuterà anche a evitare il tipo di comportamento descritto sopra.

È importante notare che quando il codice è stato scritto utilizzando direttive posizionate al di fuori dello spazio dei nomi, è necessario prestare attenzione quando si spostano queste direttive all'interno dello spazio dei nomi, per assicurarsi che ciò non cambi la semantica del codice. Come spiegato sopra, l'inserimento delle direttive using-alias all'interno dell'elemento namespace consente al compilatore di scegliere tra tipi in conflitto in modi che non accadranno quando le direttive vengono posizionate al di fuori dello spazio dei nomi.

Come correggere le violazioni Per correggere una violazione di questa regola, spostare tutte le direttive using e using-alias all'interno dell'elemento namespace.


1
@Jared - come ho notato nella mia risposta, la mia soluzione / soluzione preferita è quella di avere solo una classe per file. Penso che questa sia una convenzione abbastanza comune.
benPearce,

24
In effetti, è anche una regola StyleCop! SA1402: Il documento AC # può contenere solo una singola classe a livello di root a meno che tutte le classi siano parziali e dello stesso tipo. Una vetrina per una regola rompendo un'altra gocciola con salsa sbagliata.
Compito

6
È stato votato per essere la prima risposta a coprire effettivamente dal punto di vista StyleCop. Personalmente mi piace la sensazione visiva di usings al di fuori dello spazio dei nomi. Inner usings mi sembra così brutto. :)
nawfal,

2
Finalmente una buona risposta alla domanda. E il commento di benPearce è irrilevante ... questo non ha nulla a che fare con il numero di classi nel file.
Jim Balter,

35

Si è verificato un problema con l'inserimento delle istruzioni using nello spazio dei nomi quando si desidera utilizzare gli alias. L'alias non beneficia delle usingdichiarazioni precedenti e deve essere pienamente qualificato.

Prendere in considerazione:

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

    class MyClass
    {
    }
}

contro:

using System;

namespace MyNamespace
{
    using MyAlias = DateTime;

    class MyClass
    {
    }
}

Questo può essere particolarmente pronunciato se si dispone di un alias prolisso come il seguente (che è come ho trovato il problema):

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

Con le usingistruzioni all'interno dello spazio dei nomi, diventa improvvisamente:

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

Non carino.


1
Hai classbisogno di un nome (identificativo). Non puoi avere una usingdirettiva all'interno di una classe come indichi. Deve trovarsi a livello di spazio dei nomi, ad esempio al di fuori di quello più esterno namespaceo semplicemente all'interno di quello più interno namespace(ma non all'interno di una classe / interfaccia / ecc.).
Jeppe Stig Nielsen,

@JeppeStigNielsen Grazie. Ho smarrito le usingdirettive per errore. L'ho modificato come volevo che fosse. Grazie per averlo segnalato. Il ragionamento è comunque lo stesso.
Neo,

4

Come diceva Jeppe Stig Nielsen , questa discussione ha già ottime risposte, ma ho pensato che valesse la pena menzionare anche questa sottigliezza piuttosto ovvia.

using le direttive specificate negli spazi dei nomi possono rendere il codice più breve poiché non devono essere pienamente qualificate come quando sono specificate all'esterno.

Il seguente esempio funziona perché i tipi Fooe Barsono entrambi nello stesso namespace globale, Outer.

Presumi il file di codice Foo.cs :

namespace Outer.Inner
{
    class Foo { }
}

E Bar.cs :

namespace Outer
{
    using Outer.Inner;

    class Bar
    {
        public Foo foo;
    }
}

Ciò potrebbe omettere lo spazio dei nomi esterno nella usingdirettiva, in breve:

namespace Outer
{
    using Inner;

    class Bar
    {
        public Foo foo;
    }
}

8
È vero che "potresti omettere lo spazio dei nomi esterno", ma ciò non significa che dovresti. Per me, questo è un altro argomento sul perché usare le direttive (oltre agli alias come nella risposta di @ Neo) dovrebbe andare fuori dallo spazio dei nomi, per forzare i nomi completi dello spazio dei nomi.
Keith Robertson,

4

Mi sono imbattuto in una ruga (che non è trattata in altre risposte):

Supponiamo di avere questi spazi dei nomi:

  • Something.Other
  • Parent.Something.Other

Quando lo usi using Something.Other al di fuori di a namespace Parent, si riferisce al primo (Something.Altri).

Tuttavia, se lo usi all'interno di quella dichiarazione dello spazio dei nomi, si riferisce al secondo (Parent.Something.Other)!

C'è una soluzione semplice: aggiungi il global::prefisso " ": docs

namespace Parent
{
   using global::Something.Other;
   // etc
}

2

Le ragioni tecniche sono discusse nelle risposte e penso che alla fine si tratti delle preferenze personali poiché la differenza non è così grande e ci sono dei compromessi per entrambi. Il modello predefinito di Visual Studio per la creazione di .csfile utilizza usingdirettive al di fuori degli spazi dei nomi, ad es

Si può regolare stylecop per controllare le usingdirettive al di fuori degli spazi dei nomi aggiungendo il stylecop.jsonfile nella radice del file di progetto con il seguente:

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

Puoi creare questo file di configurazione a livello di soluzione e aggiungerlo ai tuoi progetti come "File di collegamento esistente" per condividere la configurazione anche su tutti i tuoi progetti.


2

Un'altra sottigliezza che non credo sia stata coperta dalle altre risposte è per quando hai una classe e uno spazio dei nomi con lo stesso nome.

Quando hai l'importazione all'interno dello spazio dei nomi, troverà la classe. Se l'importazione è al di fuori dello spazio dei nomi, l'importazione verrà ignorata e la classe e lo spazio dei nomi devono essere pienamente qualificati.

//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.
        }
    }
}

-8

È una pratica migliore se quelli predefiniti che utilizzano, ad esempio, " riferimenti " utilizzati nella soluzione di origine dovrebbero essere al di fuori degli spazi dei nomi e quelli che sono "nuovo riferimento aggiunto" è una buona pratica se si dovrebbe inserirlo all'interno dello spazio dei nomi. Questo per distinguere quali riferimenti vengono aggiunti.


6
No, in realtà questa è una cattiva idea. Non si deve basare la posizione tra ambito locale e ambito globale dell'utilizzo delle direttive sul fatto che sono state aggiunte di recente o meno. Invece, è buona prassi alfabetizzarli, ad eccezione dei riferimenti BCL, che dovrebbero andare in cima.
Abele,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.