Domanda

Ho eseguito StyleCop su un codice C # e continua a segnalare che il mio l'utilizzo delle direttive dovrebbe trovarsi nello spazio dei nomi.

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

È stato utile?

Soluzione

In realtà c'è 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 Outer prima di guardare quelle usando direttive al di fuori dello spazio dei nomi, quindi trova Outer.Math invece di Sistema .Math . Sfortunatamente (o forse per fortuna?), Outer.Math non ha membri PI , quindi File1 ora è danneggiato.

Questo cambia se inserisci usando nella tua 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 System prima di cercare Outer , trova System.Math e tutto va bene.

Alcuni sostengono che Math potrebbe essere un brutto nome per una classe definita dall'utente, poiché ce n'è già una in Sistema ; il punto qui è solo che è una differenza e influenza la manutenibilità del tuo codice.

È anche interessante notare cosa succede se Foo si trova nello spazio dei nomi Outer , anziché Outer.Inner . In tal caso, l'aggiunta di Outer.Math in File2 interrompe File1 indipendentemente da dove va usando . Ciò implica che il compilatore cerca nello spazio dei nomi che racchiude più interno prima di esaminare qualsiasi direttiva using .

Altri suggerimenti

Questa discussione ha già delle ottime risposte, ma credo di poter aggiungere qualche dettaglio in più 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 desideri, puoi mettere usando le direttive su tutti questi livelli. (Certo, vogliamo avere usando s in un solo posto, ma sarebbe legale secondo la lingua.)

La regola per risolvere quale tipo è implicito, può essere liberamente definita in questo modo: Per prima cosa cerca l'ambito più "interno" per una partita, se non viene trovato nulla, vai di un livello al prossimo ambito 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, arrenditi (errore di compilazione).

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

(1) Con utilizzi esterni:

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 che tipo di Ambiguous , la ricerca procede in questo ordine:

  1. Tipi nidificati all'interno di C (compresi i tipi nidificati ereditati)
  2. Tipi nello spazio dei nomi corrente MyCorp.TheProduct.SomeModule.Utilities
  3. Tipi nello spazio dei nomi MyCorp.TheProduct.SomeModule
  4. Digita MyCorp.TheProduct
  5. Digita in MyCorp
  6. Digita lo 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 e ThirdParty

L'altra convenzione:

(2) Con utilizzo 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 Ambiguous va in questo ordine:

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

(Nota che MyCorp.TheProduct faceva parte di "3." e quindi non era necessario tra "4." e "5.".

Osservazioni conclusive

Non importa se si inseriscono gli utilizzi 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 con priorità più elevata.

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 gli utilizzi.

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

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

Mettendolo all'interno degli spazi dei nomi rende le dichiarazioni locali a quello 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 andare fuori o dentro lo 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;
}

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.

Secondo la documentazione StyleCop:

SA1200: UsingDirectivesMustBePlacedWithinNamespace

Causa Una direttiva che usa C # è posta al di fuori di un elemento dello spazio dei nomi.

Descrizione della regola Una violazione di questa regola si verifica quando una direttiva using o 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 seguente codice, 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 segnala 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. Multiple Namespace

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ò riguarderà strettamente gli spazi dei nomi e aiuterà anche ad 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 risolvere le violazioni Per correggere una violazione di questa regola, sposta tutto usando le direttive using e alias all'interno dell'elemento namespace.

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 precedenti utilizzando e deve essere pienamente qualificato.

Si consideri:

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

    class MyClass
    {
    }
}

vs

using System;

namespace MyNamespace
{
    using MyAlias = DateTime;

    class MyClass
    {
    }
}

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

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

Con usando 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.

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

Le direttive

utilizzando specificate all'interno degli spazi dei nomi possono rendere il codice più breve poiché non devono essere pienamente qualificate come quando sono specificate all'esterno.

L'esempio seguente funziona perché i tipi Foo e Bar sono entrambi nello stesso spazio dei nomi 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 direttiva using , in breve:

namespace Outer
{
    using Inner;

    class Bar
    {
        public Foo foo;
    }
}

Una ruga in cui mi sono imbattuto (che non è trattato in altre risposte):

Supponi di avere questi spazi dei nomi:

  • Something.Other
  • Parent.Something.Other

Quando usi usando Something.Other outside di un namespace Parent , si riferisce al primo (Something.Other).

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

Esiste una soluzione semplice: aggiungi il " global :: " prefisso: docs

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

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 nello spazio dei nomi, allora 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.
        }
    }
}

I motivi tecnici sono discussi nelle risposte e penso che alla fine si tratti delle preferenze personali poiché la differenza non è che grande e ci sono dei compromessi per entrambi. Il modello predefinito di Visual Studio per la creazione di file .cs utilizza utilizzando direttive al di fuori degli spazi dei nomi, ad esempio

È possibile regolare stylecop per controllare usando direttive al di fuori degli spazi dei nomi aggiungendo il file stylecop.json nella radice del file di progetto con quanto segue:

{
  "$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.

È una pratica migliore se quelli predefiniti utilizzano, ad esempio, " riferimenti " utilizzato nella soluzione di origine dovrebbe essere al di fuori degli spazi dei nomi e quelli che sono " nuovo riferimento aggiunto " è una buona pratica se dovresti inserirli nello spazio dei nomi. Questo per distinguere quali riferimenti vengono aggiunti.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top