Les directives «using» devraient-elles être à l'intérieur ou à l'extérieur de l'espace de noms?

2063

J'ai exécuté StyleCop sur du code C #, et il continue de signaler que mes usingdirectives devraient être à l'intérieur de l'espace de noms.

Y a-t-il une raison technique pour placer les usingdirectives à l'intérieur plutôt qu'à l'extérieur de l'espace de noms?

benPearce
la source
4
Parfois, cela fait la différence où vous placez les utilisations: stackoverflow.com/questions/292535/linq-to-sql-designer-bug
gius
82
Juste pour référence, il y a des implications au-delà de la simple question de plusieurs classes par fichier, donc si vous êtes nouveau à cette question, veuillez continuer à lire.
Charlie
3
@ user-12506 - cela ne fonctionne pas très bien dans une équipe de développement moyenne à grande où un certain niveau de cohérence du code est requis. Et comme indiqué précédemment, si vous ne comprenez pas les différentes dispositions, vous pouvez trouver des cas de bord qui ne fonctionnent pas comme prévu.
benPearce
35
Terminologie: Ce ne sont pas des using déclarations ; ce sont des using directives . Une usinginstruction, d'autre part, est une structure de langage qui se produit avec d'autres instructions à l'intérieur d'un corps de méthode, etc. À titre d'exemple, using (var e = s.GetEnumerator()) { /* ... */ }est une instruction qui est à peu près la même que var e = s.GetEnumerator(); try { /* ... */ } finally { if (e != null) { e.Dispose(); } }.
Jeppe Stig Nielsen
1
Si cela n'a déjà été mentionné par personne, Microsoft recommande également de mettre des usingdéclarations dans les namespacedéclarations, dans leurs lignes directrices de codage internes
user1451111

Réponses:

2134

Il y a en fait une différence (subtile) entre les deux. Imaginez que vous ayez le code suivant dans File1.cs:

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

Imaginez maintenant que quelqu'un ajoute un autre fichier (File2.cs) au projet qui ressemble à ceci:

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

Le compilateur recherche Outeravant de regarder ces usingdirectives en dehors de l'espace de noms, donc il les trouve à la Outer.Mathplace de System.Math. Malheureusement (ou peut-être heureusement?), N'a Outer.Mathaucun PImembre, donc File1 est maintenant cassé.

Cela change si vous placez l' usingintérieur de votre déclaration d'espace de noms, comme suit:

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

Maintenant, le compilateur recherche Systemavant de chercher Outer, trouve System.Mathet tout va bien.

Certains diront que cela Mathpourrait être un mauvais nom pour une classe définie par l'utilisateur, car il y en a déjà un System; le point ici est juste qu'il y a une différence, et elle affecte la maintenabilité de votre code.

Il est également intéressant de noter ce qui se passe si Fooest dans l'espace de noms Outerplutôt que Outer.Inner. Dans ce cas, l'ajout Outer.Mathde File2 interrompt File1, peu importe où cela usingva. Cela implique que le compilateur recherche l'espace de noms englobant le plus intérieur avant d'examiner toute usingdirective.

Charlie
la source
28
C'est à mon avis une bien meilleure raison de placer l'utilisation d'instructions localement que l'argument de plusieurs espaces de noms en un seul fichier de Mark. En particulier, la compilation peut et se plaindra du conflit de noms (voir la documentation StyleCop pour cette règle (par exemple, telle que publiée par Jared)).
David Schmitt
148
La réponse acceptée est bonne, mais cela me semble être une bonne raison de placer les clauses using hors de l'espace de noms. Si je suis dans l'espace de noms Outer.Inner, je m'attendrais à ce qu'il utilise la classe Math d'Outer.Inner et non System.Math.
Frank Wallis
7
Je suis également d'accord avec cela. La réponse acceptée est correcte en ce qu'elle décrit techniquement la différence. Cependant, l'une ou l'autre classe aura besoin d'une légende explicite. Je préférerais que "Math" soit résolu dans ma propre classe locale, et "System.Math" se réfère à la classe externe - même si System.Math était utilisé comme "Math" avant qu'Outer.Math n'existe. Oui, il est plus difficile de corriger autant de références préexistantes, mais cela pourrait également être un indice que peut-être Outer.Math devrait avoir un nom différent!
mbmcavoy
13
Excellente réponse, mais il me semble que je ne voudrais que placer localement des instructions non-framework et garder le framework utilisant des instructions global. Quelqu'un a-t-il d'autres explications pour lesquelles je devrais complètement changer ma préférence? D'où cela vient-il également, les modèles de VS2008 utilisent-ils en dehors de l'espace de noms?
Thymine
31
Je pense que c'est plus une mauvaise convention de dénomination que de changer le lieu de votre utilisation. Il ne devrait pas y avoir de classe appelée Math dans votre solution
jDeveloper
455

Ce fil a déjà d'excellentes réponses, mais je pense que je peux apporter un peu plus de détails avec cette réponse supplémentaire.

Tout d'abord, rappelez-vous qu'une déclaration d'espace de noms avec des points, comme:

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

est entièrement équivalent à:

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

Si vous le vouliez, vous pourriez mettre des usingdirectives à tous ces niveaux. (Bien sûr, nous voulons avoir des usings dans un seul endroit, mais ce serait légal selon la langue.)

La règle de résolution du type implicite peut être énoncée de la manière suivante: commencez par rechercher la "portée" la plus interne pour une correspondance, si rien n'y est trouvé, sortez d'un niveau à la portée suivante et recherchez-y, et ainsi de suite , jusqu'à ce qu'une correspondance soit trouvée. Si, à un certain niveau, plusieurs correspondances sont trouvées, si l'un des types provient de l'assembly actuel, sélectionnez-le et émettez un avertissement du compilateur. Sinon, abandonnez (erreur de compilation).

Maintenant, soyons explicites sur ce que cela signifie dans un exemple concret avec les deux principales conventions.

(1) Avec des utilisations à l'extérieur:

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

Dans le cas ci-dessus, pour savoir de quel type il Ambiguouss'agit, la recherche se déroule dans cet ordre:

  1. Types imbriqués à l'intérieur C(y compris les types imbriqués hérités)
  2. Types dans l'espace de noms actuel MyCorp.TheProduct.SomeModule.Utilities
  3. Types dans l'espace de noms MyCorp.TheProduct.SomeModule
  4. Types dans MyCorp.TheProduct
  5. Types dans MyCorp
  6. Types dans l' espace de noms nul (l'espace de noms global)
  7. Types System, System.Collections.Generic, System.Linq, MyCorp.TheProduct.OtherModule, MyCorp.TheProduct.OtherModule.IntegrationetThirdParty

L'autre convention:

(2) Avec des utilisations à l'intérieur:

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

Maintenant, recherchez le type Ambiguousdans cet ordre:

  1. Types imbriqués à l'intérieur C(y compris les types imbriqués hérités)
  2. Types dans l'espace de noms actuel MyCorp.TheProduct.SomeModule.Utilities
  3. Types System, System.Collections.Generic, System.Linq, MyCorp.TheProduct, MyCorp.TheProduct.OtherModule, MyCorp.TheProduct.OtherModule.IntegrationetThirdParty
  4. Types dans l'espace de noms MyCorp.TheProduct.SomeModule
  5. Types dans MyCorp
  6. Types dans l' espace de noms nul (l'espace de noms global)

(Notez que cela MyCorp.TheProductfaisait partie de "3." et n'était donc pas nécessaire entre "4." et "5.".)

Remarques finales

Peu importe si vous placez les utilisations à l'intérieur ou à l'extérieur de la déclaration d'espace de noms, il est toujours possible que quelqu'un ajoute plus tard un nouveau type avec un nom identique à l'un des espaces de noms qui ont une priorité plus élevée.

En outre, si un espace de noms imbriqué a le même nom qu'un type, il peut provoquer des problèmes.

Il est toujours dangereux de déplacer les utilisations d'un emplacement à un autre car la hiérarchie de recherche change et un autre type peut être trouvé. Par conséquent, choisissez une convention et respectez-la, de sorte que vous n'aurez jamais à déplacer les utilisations.

Les modèles de Visual Studio, par défaut, mettent les utilisations à l' extérieur de l'espace de noms (par exemple si vous faites VS générer une nouvelle classe dans un nouveau fichier).

Un (petit) avantage d'avoir des utilisations à l' extérieur est que vous pouvez ensuite utiliser les directives using pour un attribut global, par exemple [assembly: ComVisible(false)]au lieu de [assembly: System.Runtime.InteropServices.ComVisible(false)].

Jeppe Stig Nielsen
la source
46
C'est la meilleure explication, car elle met en évidence le fait que la position des déclarations «using» est une décision délibérée du développeur. En aucun cas, une personne ne devrait négligemment modifier l'emplacement des déclarations «d'utilisation» sans en comprendre les implications. Par conséquent, la règle StyleCop est simplement stupide.
ZunTzu
194

Le placer à l'intérieur des espaces de noms rend les déclarations locales à cet espace de noms pour le fichier (dans le cas où vous avez plusieurs espaces de noms dans le fichier) mais si vous n'avez qu'un seul espace de noms par fichier, cela ne fait pas beaucoup de différence, qu'ils sortent ou à l'intérieur de l'espace de noms.

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
la source
les espaces de noms fournissent une séparation logique et non physique (fichier).
Jowen
9
Ce n'est pas tout à fait vrai qu'il n'y a pas de différence; usingles directives dans les namespaceblocs peuvent faire référence à des espaces de noms relatifs basés sur le namespacebloc englobant .
OR Mapper
70
Ouais je sais. nous avons établi cela dans la réponse acceptée de cette question il y a cinq ans.
Mark Cidade
59

Selon Hanselman - Utilisation de la directive et du chargement d'assemblage ... et d'autres articles de ce type, il n'y a techniquement aucune différence.

Ma préférence est de les mettre en dehors des espaces de noms.

Quintin Robinson
la source
3
@Chris M: euh ... le lien affiché dans la réponse indique qu'il n'y a aucun avantage à entrer ou sortir, montrant en fait un exemple qui fausse la réclamation faite dans le lien que vous avez publié ...
johnny
2
Oui, je n'ai pas entièrement lu le fil, mais j'ai accepté lorsque les MVP ont dit que c'était vrai. Un gars le réfute, l'explique et montre son code plus bas ... "L'IL que le compilateur C # génère est le même dans les deux cas. En fait, le compilateur C # ne génère précisément rien correspondant à chaque directive using. Les directives using sont purement un C # isme, et ils n'ont aucune signification pour .NET lui-même. (Pas vrai pour utiliser des instructions mais ce sont des choses très différentes.) " Groups.google.com/group/wpf-disciples/msg/781738deb0a15c46
Chris McKee
84
Veuillez inclure un résumé du lien. Lorsque le lien est rompu (car cela se produira, avec suffisamment de temps), soudainement une réponse avec 32 votes positifs ne vaut que My style is to put them outside the namespaces.- à peine une réponse du tout.
ANeves
11
L'affirmation ici est tout simplement fausse ... il y a une différence technique et votre propre citation le dit ... en fait, c'est de cela qu'il s'agit. Veuillez supprimer cette réponse erronée ... il y en a de bien meilleures et précises.
Jim Balter
53

Selon la documentation StyleCop:

SA1200: Utilisation de DirectivesMustBePlacedWithinNamespace

Cause La directive using AC # est placée en dehors d'un élément d'espace de noms.

Description de la règle Une violation de cette règle se produit lorsqu'une directive using ou une directive using-alias est placée en dehors d'un élément d'espace de noms, sauf si le fichier ne contient aucun élément d'espace de noms.

Par exemple, le code suivant entraînerait deux violations de cette règle.

using System;
using Guid = System.Guid;

namespace Microsoft.Sample
{
    public class Program
    {
    }
}

Le code suivant, cependant, n'entraînerait aucune violation de cette règle:

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

    public class Program
    {
    }
}

Ce code se compilera proprement, sans aucune erreur de compilation. Cependant, il n'est pas clair quelle version du type Guid est allouée. Si la directive using est déplacée à l'intérieur de l'espace de noms, comme illustré ci-dessous, une erreur de compilation se produira:

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

Le code échoue sur l'erreur de compilation suivante, trouvée sur la ligne contenant Guid g = new Guid("hello");

CS0576: l'espace de noms «Microsoft.Sample» contient une définition en conflit avec l'alias «Guid»

Le code crée un alias pour le type System.Guid appelé Guid et crée également son propre type appelé Guid avec une interface de constructeur correspondante. Plus tard, le code crée une instance du type Guid. Pour créer cette instance, le compilateur doit choisir entre les deux définitions différentes de Guid. Lorsque la directive using-alias est placée en dehors de l'élément namespace, le compilateur choisit la définition locale de Guid définie dans l'espace de noms local et ignore complètement la directive using-alias définie en dehors de l'espace de noms. Ce n'est malheureusement pas évident lors de la lecture du code.

Cependant, lorsque la directive using-alias est positionnée dans l'espace de noms, le compilateur doit choisir entre deux types de Guid différents et conflictuels définis dans le même espace de noms. Ces deux types fournissent un constructeur correspondant. Le compilateur n'est pas en mesure de prendre une décision, il signale donc l'erreur du compilateur.

Placer la directive using-alias en dehors de l'espace de noms est une mauvaise pratique car cela peut entraîner de la confusion dans des situations comme celle-ci, où il n'est pas évident de savoir quelle version du type est réellement utilisée. Cela peut potentiellement conduire à un bug qui pourrait être difficile à diagnostiquer.

Le fait de placer des directives using-alias dans l'élément namespace élimine cela comme source de bogues.

  1. Espaces de noms multiples

Placer plusieurs éléments d'espace de noms dans un seul fichier est généralement une mauvaise idée, mais si et quand cela est fait, c'est une bonne idée de placer toutes les directives d'utilisation dans chacun des éléments d'espace de noms, plutôt que globalement en haut du fichier. Cela limitera étroitement les espaces de noms et aidera également à éviter le type de comportement décrit ci-dessus.

Il est important de noter que lorsque le code a été écrit en utilisant des directives placées en dehors de l'espace de noms, il faut faire attention lors du déplacement de ces directives dans l'espace de noms, pour s'assurer que cela ne change pas la sémantique du code. Comme expliqué ci-dessus, le placement de directives using-alias dans l'élément namespace permet au compilateur de choisir entre des types en conflit d'une manière qui ne se produira pas lorsque les directives seront placées en dehors de l'espace de noms.

Comment corriger les violations Pour corriger une violation de cette règle, déplacez toutes les directives using et les directives using-alias dans l'élément namespace.

JaredCacurak
la source
1
@Jared - comme je l'ai noté dans ma réponse, ma solution de contournement / solution préférée est de n'avoir qu'une seule classe par fichier. Je pense que c'est une convention assez courante.
benPearce
24
En effet, c'est aussi une règle StyleCop! SA1402: Le document AC # ne peut contenir qu'une seule classe au niveau racine, sauf si toutes les classes sont partielles et sont du même type. Présenter une règle en en cassant une autre ne fait que couler avec une mauvaise sauce.
Tâche
6
Voté pour être la première réponse à le couvrir du point de vue StyleCop. Personnellement, j'aime la sensation visuelle de usings en dehors de l'espace de noms. Inner usingme semble si moche. :)
nawfal
2
Enfin une bonne réponse à la question. Et le commentaire de benPearce n'est pas pertinent ... cela n'a rien à voir avec le nombre de classes dans le fichier.
Jim Balter
35

Il y a un problème avec le placement d'instructions using dans l'espace de noms lorsque vous souhaitez utiliser des alias. L'alias ne bénéficie pas des usingdéclarations précédentes et doit être pleinement qualifié.

Considérer:

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

    class MyClass
    {
    }
}

contre:

using System;

namespace MyNamespace
{
    using MyAlias = DateTime;

    class MyClass
    {
    }
}

Cela peut être particulièrement prononcé si vous avez un alias de longue haleine tel que le suivant (c'est ainsi que j'ai trouvé le problème):

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

Avec des usinginstructions à l'intérieur de l'espace de noms, cela devient soudain:

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

Pas beau.

Néo
la source
1
Votre classbesoin d'un nom (identifiant). Vous ne pouvez pas avoir de usingdirective à l'intérieur d'une classe comme vous l'indiquez. Il doit être au niveau de l'espace de noms, par exemple en dehors de l'extérieur namespace, ou juste à l'intérieur de l'intérieur namespace(mais pas à l'intérieur d'une classe / interface / etc.).
Jeppe Stig Nielsen
@JeppeStigNielsen Merci. J'ai égaré les usingdirectives par erreur. Je l'ai édité comme je le souhaitais. Merci d'avoir souligné. Le raisonnement est toujours le même, cependant.
Neo
4

Comme Jeppe Nielsen Stig dit , ce fil a déjà de grandes réponses, mais je pensais que cette subtilité assez évidente était utile de mentionner aussi.

using Les directives spécifiées à l'intérieur des espaces de noms peuvent rendre le code plus court car elles n'ont pas besoin d'être pleinement qualifiées comme lorsqu'elles sont spécifiées à l'extérieur.

L'exemple suivant fonctionne parce que les types Fooet Barsont à la fois dans le même espace de noms global, Outer.

Supposons que le fichier de code Foo.cs :

namespace Outer.Inner
{
    class Foo { }
}

Et Bar.cs :

namespace Outer
{
    using Outer.Inner;

    class Bar
    {
        public Foo foo;
    }
}

Cela peut omettre l'espace de noms externe dans la usingdirective, pour faire court:

namespace Outer
{
    using Inner;

    class Bar
    {
        public Foo foo;
    }
}
Des biscuits
la source
8
Il est vrai que vous "pouvez omettre l'espace de noms externe", mais cela ne signifie pas que vous devriez le faire. Pour moi, c'est un autre argument pour expliquer pourquoi l'utilisation de directives (autres que les alias comme dans la réponse de @ Neo) devrait sortir de l'espace de noms, pour forcer les noms d'espace de noms pleinement qualifiés.
Keith Robertson
4

Une ride que j'ai rencontrée (qui n'est pas couverte dans d'autres réponses):

Supposons que vous ayez ces espaces de noms:

  • Quelque chose d'autre
  • Parent.Quelque chose.Autre

Lorsque vous utilisez using Something.Other dehors d'un namespace Parent, il se réfère au premier (Quelque chose.Autre).

Cependant, si vous l'utilisez dans cette déclaration d'espace de noms, elle se réfère à la seconde (Parent.Something.Other)!

Il existe une solution simple: ajoutez le global::préfixe " ": docs

namespace Parent
{
   using global::Something.Other;
   // etc
}
Hans Ke st ing
la source
2

Les raisons techniques sont discutées dans les réponses et je pense qu'en fin de compte, il s'agit des préférences personnelles, car la différence n'est pas si grande et il y a des compromis pour les deux. Le modèle par défaut de Visual Studio pour créer des .csfichiers utilise des usingdirectives en dehors des espaces de noms, par exemple

On peut ajuster stylecop pour vérifier les usingdirectives en dehors des espaces de noms en ajoutant un stylecop.jsonfichier à la racine du fichier de projet avec ce qui suit:

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

Vous pouvez créer ce fichier de configuration au niveau de la solution et l'ajouter à vos projets en tant que «fichier de lien existant» pour partager également la configuration entre tous vos projets.

sotn
la source
2

Une autre subtilité que je ne pense pas avoir été couverte par les autres réponses est quand vous avez une classe et un espace de noms avec le même nom.

Lorsque vous avez l'importation à l'intérieur de l'espace de noms, il trouvera la classe. Si l'importation est en dehors de l'espace de noms, l'importation sera ignorée et la classe et l'espace de noms doivent être pleinement qualifiés.

//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
la source
-8

C'est une meilleure pratique si ceux qui utilisent par défaut les « références » utilisées dans votre solution source doivent être en dehors des espaces de noms et ceux qui sont de «nouvelles références ajoutées» est une bonne pratique si vous devez les mettre à l'intérieur de l'espace de noms. C'est pour distinguer quelles références sont ajoutées.

Israel Ocbina
la source
6
Non, en fait, c'est une mauvaise idée. Vous ne devez pas baser l'emplacement entre la portée locale et la portée mondiale de l'utilisation de directives sur le fait qu'elles ont été ajoutées ou non. Au lieu de cela, il est recommandé de les classer par ordre alphabétique, à l'exception des références BCL, qui doivent être placées en haut.
Abel