web-dev-qa-db-de.com

Sollten Direktiven innerhalb oder außerhalb des Namespaces verwendet werden?

Ich habe StyleCop über einen C # -Code ausgeführt und es wird weiterhin gemeldet, dass sich meine using -Direktiven im Namespace befinden sollten.

Gibt es einen technischen Grund, die Anweisungen using innerhalb und nicht außerhalb des Namespaces zu platzieren?

1927
benPearce

Es gibt tatsächlich einen (subtilen) Unterschied zwischen den beiden. Stellen Sie sich vor, Sie haben den folgenden Code in File1.cs:

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

Stellen Sie sich nun vor, jemand fügt dem Projekt eine weitere Datei (File2.cs) hinzu, die so aussieht:

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

Der Compiler sucht nach Outer, bevor er sich diese using Direktiven außerhalb des Namespaces ansieht, sodass er Outer.Math anstelle von System.Math findet. Leider (oder vielleicht zum Glück?) Hat Outer.Math kein PI Mitglied, daher ist File1 jetzt defekt.

Dies ändert sich, wenn Sie using wie folgt in Ihre Namespace-Deklaration einfügen:

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

Nun sucht der Compiler nach System, bevor er nach Outer sucht, findet System.Math und alles ist in Ordnung.

Einige würden argumentieren, dass Math ein falscher Name für eine benutzerdefinierte Klasse sein könnte, da es in System bereits einen gibt; Hier geht es nur darum, dass ist ein Unterschied vorliegt, der sich auf die Wartbarkeit Ihres Codes auswirkt.

Es ist auch interessant zu wissen, was passiert, wenn Foo im Namespace Outer und nicht Outer.Inner ist. In diesem Fall unterbricht das Hinzufügen von Outer.Math in Datei2 Datei1, unabhängig davon, wohin das using geht. Dies impliziert, dass der Compiler den innersten einschließenden Namespace durchsucht, bevor er eine using -Direktive betrachtet.

2012
Charlie

Dieser Thread hat bereits einige großartige Antworten, aber ich glaube, ich kann mit dieser zusätzlichen Antwort ein wenig mehr Details bringen.

Denken Sie zunächst daran, dass eine Namespace-Deklaration mit Punkten wie folgt lautet:

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

ist völlig gleichbedeutend mit:

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

Wenn Sie möchten, können Sie using Direktiven auf all diese Ebenen setzen. (Natürlich möchten wir usings nur an einem Ort haben, aber es wäre laut der Sprache legal.)

Die Regel zum Auflösen des implizierten Typs kann lose wie folgt angegeben werden: Suchen Sie zuerst den innersten "Bereich" nach einer Übereinstimmung, wenn dort nichts gefunden wird, gehen Sie eine Ebene zum nächsten Bereich und suchen Sie dort. und so weiter, bis eine Übereinstimmung gefunden wird. Wenn auf einer bestimmten Ebene mehr als eine Übereinstimmung gefunden wird und einer der Typen aus der aktuellen Assembly stammt, wählen Sie diesen aus und geben Sie eine Compiler-Warnung aus. Andernfalls geben Sie auf (Fehler bei der Kompilierung).

Lassen Sie uns nun anhand eines konkreten Beispiels mit den beiden wichtigsten Konventionen erläutern, was dies bedeutet.

(1) Bei Verwendung außerhalb:

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

Um herauszufinden, welcher Typ Ambiguous ist, wird im obigen Fall in der folgenden Reihenfolge gesucht:

  1. Verschachtelte Typen in C (einschließlich vererbter verschachtelter Typen)
  2. Schreibt im aktuellen Namensraum MyCorp.TheProduct.SomeModule.Utilities
  3. Typen im Namensraum MyCorp.TheProduct.SomeModule
  4. Tippen Sie MyCorp.TheProduct ein
  5. Typen in MyCorp
  6. Geben Sie den null Namespace (den globalen Namespace) ein.
  7. Geben Sie System, System.Collections.Generic, System.Linq, MyCorp.TheProduct.OtherModule, MyCorp.TheProduct.OtherModule.Integration und ThirdParty ein

Die andere Konvention:

(2) Bei Verwendung in:

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

Suchen Sie nun nach dem Typ Ambiguous in der folgenden Reihenfolge:

  1. Verschachtelte Typen in C (einschließlich vererbter verschachtelter Typen)
  2. Schreibt im aktuellen Namensraum MyCorp.TheProduct.SomeModule.Utilities
  3. Geben Sie System, System.Collections.Generic, System.Linq, MyCorp.TheProduct, MyCorp.TheProduct.OtherModule, MyCorp.TheProduct.OtherModule.Integration und ThirdParty ein
  4. Typen im Namensraum MyCorp.TheProduct.SomeModule
  5. Typen in MyCorp
  6. Geben Sie den null Namespace (den globalen Namespace) ein.

(Beachten Sie, dass MyCorp.TheProduct Teil von "3." war und daher zwischen "4." und "5." nicht benötigt wurde.)

abschließende Bemerkungen

Egal, ob Sie die Verwendungen innerhalb oder außerhalb der Namespace-Deklaration platzieren, es besteht immer die Möglichkeit, dass später jemand einem der Namespaces mit höherer Priorität einen neuen Typ mit identischem Namen hinzufügt.

Wenn ein verschachtelter Namespace denselben Namen wie ein Typ hat, kann dies zu Problemen führen.

Es ist immer gefährlich, die Verwendungen von einem Ort an einen anderen zu verschieben, da sich die Suchhierarchie ändert und möglicherweise ein anderer Typ gefunden wird. Wählen Sie daher eine Konvention und halten Sie sich an diese, damit Sie nie wieder neue Einstellungen vornehmen müssen.

In den Vorlagen von Visual Studio werden die Verwendungen standardmäßig außerhalb des Namespaces platziert (z. B. wenn Sie VS veranlassen, eine neue Klasse in einer neuen Datei zu generieren).

Ein (winziger) Vorteil von Usings außerhalb von ist, dass Sie dann die using-Direktiven für ein globales Attribut verwenden können, z. B. [Assembly: ComVisible(false)] anstelle von [Assembly: System.Runtime.InteropServices.ComVisible(false)].

412

Durch das Einfügen in die Namespaces werden die Deklarationen für diesen Namespace für die Datei lokal (falls Sie mehrere Namespaces in der Datei haben). Wenn Sie jedoch nur einen Namespace pro Datei haben, spielt es keine große Rolle, ob sie nach draußen oder nach draußen gehen innerhalb des Namensraums.

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

Nach Hanselman - Using Directive and Assembly Loading ... und anderen solchen Artikeln gibt es technisch keinen Unterschied.

Ich bevorzuge es, sie außerhalb von Namespaces zu platzieren.

62

Gemäß der StyleCop-Dokumentation:

SA1200: UsingDirectivesMustBePlacedWithinNamespace

Ursache Eine C # using-Direktive wird außerhalb eines Namespace-Elements platziert.

Regelbeschreibung Ein Verstoß gegen diese Regel tritt auf, wenn eine using-Direktive oder eine using-Alias-Direktive außerhalb eines Namespace-Elements platziert wird, es sei denn, die Datei enthält keine Namespace-Elemente.

Der folgende Code würde beispielsweise zu zwei Verstößen gegen diese Regel führen.

using System;
using Guid = System.Guid;

namespace Microsoft.Sample
{
    public class Program
    {
    }
}

Der folgende Code würde jedoch zu keinen Verstößen gegen diese Regel führen:

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

    public class Program
    {
    }
}

Dieser Code wird sauber kompiliert, ohne dass Compilerfehler auftreten. Es ist jedoch unklar, welche Version des Guid-Typs zugewiesen wird. Wenn die using-Direktive wie unten gezeigt in den Namespace verschoben wird, tritt ein Compiler-Fehler auf:

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

Der Code schlägt bei dem folgenden Compilerfehler fehl, der in der Zeile gefunden wird, die Guid g = new Guid("hello"); enthält

CS0576: Der Namespace "Microsoft.Sample" enthält eine Definition, die mit dem Alias ​​"Guid" in Konflikt steht

Der Code erstellt einen Alias ​​für den Typ "System.Guid" mit dem Namen "Guid" und einen eigenen Typ mit dem Namen "Guid" mit einer entsprechenden Konstruktorschnittstelle. Später erstellt der Code eine Instanz vom Typ Guid. Um diese Instanz zu erstellen, muss der Compiler zwischen den beiden verschiedenen Definitionen von Guid wählen. Wenn die using-alias-Direktive außerhalb des Namespace-Elements platziert wird, wählt der Compiler die lokale Definition der Guid aus, die im lokalen Namespace definiert ist, und ignoriert die using-alias-Direktive, die außerhalb des Namespace definiert ist, vollständig. Dies ist beim Lesen des Codes leider nicht ersichtlich.

Wenn sich die using-alias-Direktive jedoch im Namespace befindet, muss der Compiler zwischen zwei verschiedenen, in Konflikt stehenden Guid-Typen wählen, die beide im selben Namespace definiert sind. Beide Typen bieten einen passenden Konstruktor. Der Compiler kann keine Entscheidung treffen und markiert den Compilerfehler.

Das Platzieren der using-alias-Direktive außerhalb des Namespace ist eine schlechte Praxis, da dies in Situationen wie diesen zu Verwirrung führen kann, in denen nicht klar ist, welche Version des Typs tatsächlich verwendet wird. Dies kann möglicherweise zu einem Fehler führen, der möglicherweise schwer zu diagnostizieren ist.

Durch das Platzieren von using-alias-Direktiven im Namespace-Element wird diese Fehlerquelle beseitigt.

  1. Mehrere Namespaces

Das Platzieren mehrerer Namespace-Elemente in einer einzelnen Datei ist im Allgemeinen eine schlechte Idee. In diesem Fall empfiehlt es sich jedoch, alle using-Anweisungen innerhalb der Namespace-Elemente zu platzieren und nicht global am Anfang der Datei. Dadurch werden die Namespaces eng abgegrenzt und das oben beschriebene Verhalten vermieden.

Es ist wichtig zu beachten, dass beim Schreiben von Code mit Direktiven, die außerhalb des Namespaces platziert wurden, beim Verschieben dieser Direktiven innerhalb des Namespaces Vorsicht geboten ist, um sicherzustellen, dass dadurch die Semantik des Codes nicht geändert wird. Wie oben erläutert, kann der Compiler beim Platzieren von using-alias-Direktiven innerhalb des Namespace-Elements zwischen Typen mit Konflikten wählen, wie dies nicht der Fall ist, wenn die Direktiven außerhalb des Namespace platziert werden.

Beheben von Verstößen Um einen Verstoß gegen diese Regel zu beheben, verschieben Sie alle using-Direktiven und using-Alias-Direktiven innerhalb des Namespace-Elements.

47
JaredCacurak

Es gibt ein Problem beim Platzieren von Anweisungen im Namespace, wenn Sie Aliase verwenden möchten. Der Alias ​​profitiert nicht von den früheren using Anweisungen und muss vollständig qualifiziert sein.

Erwägen:

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

    class MyClass
    {
    }
}

gegen:

using System;

namespace MyNamespace
{
    using MyAlias = DateTime;

    class MyClass
    {
    }
}

Dies kann besonders ausgeprägt sein, wenn Sie einen langwierigen Alias ​​wie den folgenden haben (so habe ich das Problem gefunden):

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

Mit using -Anweisungen im Namespace wird es plötzlich zu:

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

Nicht hübsch.

33
Neo

Eine Falte, auf die ich gestoßen bin (die in anderen Antworten nicht behandelt wird):

Angenommen, Sie haben folgende Namespaces:

  • Etwas anderes
  • Eltern.Etwas.Anderes

Wenn Sie using Something.Otheroutside eines namespace Parent verwenden, bezieht sich dies auf den ersten (Something.Other).

Wenn Sie es jedoch innerhalb dieser Namespace-Deklaration verwenden, bezieht es sich auf die zweite (Parent.Something.Other)!

Es gibt eine einfache Lösung: Fügen Sie das Präfix "global::" hinzu: docs

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

Als Jeppe Stig Nielsen sagte hat dieser Thread bereits großartige Antworten, aber ich dachte, diese ziemlich offensichtliche Subtilität wäre auch erwähnenswert.

using Direktiven, die in Namespaces angegeben werden, können kürzeren Code bedeuten, da sie nicht vollständig qualifiziert sein müssen, wie wenn sie an der Außenseite angegeben sind.

Das folgende Beispiel funktioniert, da sich die Typen Foo und Bar beide im selben globalen Namespace Outer befinden.

Angenommen, die Codedatei Foo.cs :

namespace Outer.Inner
{
    class Foo { }
}

Und Bar.cs :

namespace Outer
{
    using Outer.Inner;

    class Bar
    {
        public Foo foo;
    }
}

Das kann den äußeren Namespace in der Direktive using kurz weglassen:

namespace Outer
{
    using Inner;

    class Bar
    {
        public Foo foo;
    }
}
3
Biscuits

Eine andere Subtilität, von der ich nicht glaube, dass sie von den anderen Antworten abgedeckt wurde, ist, wenn Sie eine Klasse und einen Namespace mit demselben Namen haben.

Wenn Sie den Import im Namespace haben, findet er die Klasse. Wenn sich der Import außerhalb des Namespaces befindet, wird der Import ignoriert und die Klasse und der Namespace müssen vollständig qualifiziert sein.

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

Die technischen Gründe werden in den Antworten diskutiert und ich denke, dass es am Ende um die persönlichen Vorlieben geht, da der Unterschied nicht darin besteht, dass groß und es Kompromisse für beide gibt. Die Standardvorlage von Visual Studio zum Erstellen von .cs -Dateien verwendet using Direktiven außerhalb von Namespaces, z.

Sie können stylecop anpassen, um Anweisungen von using außerhalb von Namespaces zu überprüfen, indem Sie die Datei stylecop.json im Stammverzeichnis der Projektdatei wie folgt hinzufügen:

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

Sie können diese Konfigurationsdatei auf Lösungsebene erstellen und sie als "Vorhandene Verknüpfungsdatei" zu Ihren Projekten hinzufügen, um die Konfiguration auch für alle Ihre Projekte freizugeben.

0
sotn