web-dev-qa-db-fra.com

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

J'ai utilisé StyleCop sur du code C # et il continue de signaler que mes directives using doivent se trouver à l'intérieur de l'espace de noms.

Existe-t-il une raison technique pour insérer les directives using à la place de l'extérieur du namespace?

1927
benPearce

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

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

Maintenant, imaginons que quelqu'un ajoute un autre fichier (File2.cs) au projet qui ressemble à ceci:

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

Le compilateur recherche Outer avant d'examiner ces directives using en dehors de l'espace de noms. Il recherche donc Outer.Math au lieu de System.Math. Malheureusement (ou peut-être heureusement?), Outer.Math n'a pas de membre PI, donc File1 est maintenant cassé.

Cela change si vous mettez la using dans 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 cherche System avant de chercher Outer, trouve System.Math, et tout va bien.

Certains diront que Math pourrait être un mauvais nom pour une classe définie par l'utilisateur, puisqu'il en existe déjà un dans System; le point ici est simplement que là est une différence, et cela affecte la maintenabilité de votre code.

Il est également intéressant de noter ce qui se passe si Foo est dans l'espace de noms Outer, plutôt que Outer.Inner. Dans ce cas, l'ajout de Outer.Math dans File2 rompt File1 indépendamment de l'endroit où using va. Cela implique que le compilateur recherche dans l'espace de noms le plus interne avant de se pencher sur une directive using.

2012
Charlie

Ce fil a déjà d'excellentes réponses, mais je pense pouvoir 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 souhaitez, vous pouvez mettre des directives using à tous ces niveaux. (Bien entendu, nous voulons avoir usings dans un seul endroit, mais ce serait légal selon la langue.)

La règle de résolution du type impliqué implique l’énoncé suivant: Commencez par rechercher le "champ" le plus interne pour une correspondance. Si rien n’y est trouvé, sortez d’un niveau à l’autre et chercher là-bas, et ainsi de suite , jusqu'à ce qu'une correspondance soit trouvée. Si à un certain niveau plus d'une correspondance est trouvée, 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, expliquons ce que cela signifie dans un exemple concret avec les deux conventions principales.

(1) Avec les 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 rechercher le type Ambiguous, procédez comme suit:

  1. Types imbriqués dans 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 null (espace de noms global)
  7. Types dans System, System.Collections.Generic, System.Linq, MyCorp.TheProduct.OtherModule, MyCorp.TheProduct.OtherModule.Integration et ThirdParty

L'autre convention:

(2) Avec les 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 Ambiguous va dans cet ordre:

  1. Types imbriqués dans 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 System, System.Collections.Generic, System.Linq, MyCorp.TheProduct, MyCorp.TheProduct.OtherModule, MyCorp.TheProduct.OtherModule.Integration et ThirdParty
  4. Types dans l'espace de noms MyCorp.TheProduct.SomeModule
  5. Types dans MyCorp
  6. Types dans l'espace de noms null (espace de noms global)

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

Remarques finales

Peu importe si vous insérez les utilisations dans ou en dehors de la déclaration d'espace de noms, il est toujours possible que quelqu'un ajoute ensuite un nouveau type avec un nom identique à l'un des espaces de noms ayant une priorité supérieure.

En outre, si un espace de noms imbriqué a le même nom qu'un type, cela peut entraîner 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, afin de ne pas avoir à déplacer les utilisateurs.

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

Un (petit) avantage d'avoir des utilisations en dehors de 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)].

412
Jeppe Stig Nielsen

Le placer à l'intérieur des espaces de noms rend les déclarations locales à cet espace de noms pour le fichier (si vous avez plusieurs espaces de noms dans le fichier), mais si vous n'avez qu'un seul espace de noms par fichier, cela ne changera pas grand-chose qu'ils soient en dehors 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;
}
189
Mark Cidade

Selon Hanselman - Utilisation de la directive et de l'assemblage Chargement ... et d'autres articles du même genre, il n'y a techniquement aucune différence.

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

62
Quintin Robinson

Selon la documentation StyleCop:

SA1200: Utilisation deDirectivesMustBePlacedWithinNamespace

Cause Une directive using C # 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 nom.

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, toutefois, n'entraînera 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, la version du type Guid allouée n’est pas clairement définie. Si la directive using est déplacée à l'intérieur de l'espace de noms, comme indiqué ci-dessous, une erreur du compilateur 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 lors de 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 à l'extérieur 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 à l'extérieur de l'espace de noms. Malheureusement, ceci n’est pas évident lors de la lecture du code.

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 en conflit, tous deux définis dans le même espace de noms. Ces deux types fournissent un constructeur correspondant. Le compilateur étant incapable de prendre une décision, il signale l'erreur du compilateur.

Placer la directive using-alias en dehors de l'espace de noms est une pratique déconseillée, car elle peut entraîner une 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 cette possibilité en tant que source de bogues.

  1. Plusieurs espaces de noms

Placer plusieurs éléments d’espace de noms dans un même fichier est généralement une mauvaise idée, mais si cela est fait, il est judicieux 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 couvrira étroitement les espaces de noms et contribuera également à éviter le type de comportement décrit ci-dessus.

Il est important de noter que lorsque le code a été écrit avec des directives situées en dehors de l'espace de noms, vous devez faire attention lorsque vous déplacez ces directives dans l'espace de noms, afin de vous assurer que cela ne modifie pas la sémantique du code. Comme expliqué ci-dessus, le fait de placer des 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 si les directives sont placées en dehors de l'espace de nommage.

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

47
JaredCacurak

Il y a un problème avec le placement d'instructions using à l'intérieur de l'espace de noms lorsque vous souhaitez utiliser des alias. L'alias ne bénéficie pas des instructions using antérieures 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 à longue haleine tel que celui-ci (qui explique comment j'ai trouvé le problème):

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

Avec les instructions using à l'intérieur de l'espace de noms, cela devient tout à coup:

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

Pas beau.

33
Neo

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.Autre
  • Parent.quelque chose.Autre

Lorsque vous utilisez using Something.Other à l'extérieur d'un namespace Parent, il fait référence au premier (Quelque chose.Autre).

Cependant, si vous l'utilisez à l'intérieur de cette déclaration d'espace de noms, il fait référence à la seconde (Parent.Something.Other)!

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

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

Comme Jeppe Stig Nielsen dit , ce fil a déjà de bonnes réponses, mais je pensais que cette subtilité assez évident méritait d'être mentionnée aussi.

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

L'exemple suivant fonctionne parce que les types Foo et Bar se trouvent tous deux dans le même espace de nom global, Outer.

Présumer 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 directive using, en bref:

namespace Outer
{
    using Inner;

    class Bar
    {
        public Foo foo;
    }
}
3
Biscuits

Une autre subtilité que je ne crois pas couverte par les autres réponses concerne le fait que vous ayez une classe et un espace de noms du même nom.

Lorsque vous avez importé à 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 entièrement 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.
        }
    }
}
1
Ben Gardner

Les raisons techniques sont discutées dans les réponses et je pense qu’il s’agit des préférences personnelles à la fin, car la différence n’est pas que big et il existe des compromis pour les deux. Le modèle par défaut de Visual Studio pour la création de fichiers .cs utilise les directives using en dehors des espaces de noms, par exemple.

Vous pouvez ajuster stylecop pour vérifier les directives using en dehors des espaces de noms en ajoutant le fichier stylecop.json à la racine du fichier de projet avec les éléments suivants:

{
  "$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 liaison existant" pour partager également la configuration sur tous vos projets.

0
sotn