Frage Sollten 'using'-Direktiven innerhalb oder außerhalb des Namensraums liegen?


Ich bin gerannt StilCop über einige C # -Code, und es meldet, dass meine using Direktiven sollten innerhalb des Namespace sein.

Gibt es einen technischen Grund dafür? using Direktiven innerhalb und nicht außerhalb des Namensraums?


1737
2017-09-24 03:49


Ursprung


Antworten:


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, dass jemand dem Projekt eine weitere Datei (File2.cs) hinzufügt, die wie folgt aussieht:

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

Der Compiler sucht Outer bevor ich diese betrachte using Direktiven außerhalb des Namensraums, so findet es Outer.Math Anstatt von System.Math. Leider (oder vielleicht zum Glück?), Outer.Math hat kein PI Mitglied, so ist Datei1 jetzt gebrochen.

Dies ändert sich, wenn Sie die using in Ihrer Namespace-Deklaration wie folgt:

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

Jetzt sucht der Compiler System vor dem Suchen Outer, findet System.Mathund alles ist gut.

Manche würden das bestreiten Math ist möglicherweise ein schlechter Name für eine benutzerdefinierte Klasse, da es bereits einen gibt System; Der Punkt hier ist nur das da ist ein Unterschied, und es beeinflusst die Wartbarkeit Ihres Codes.

Es ist auch interessant zu bemerken, was passiert, wenn Foo ist im Namensraum Outer, eher, als Outer.Inner. In diesem Fall hinzufügen Outer.Math in Datei2 bricht Datei1 unabhängig davon, wo die using geht. Dies bedeutet, dass der Compiler den innersten einschließenden Namespace durchsucht, bevor er einen betrachtet using Richtlinie.


1839
2017-09-30 02:33



Dieser Thread hat bereits einige gute Antworten, aber ich denke, dass ich mit dieser zusätzlichen Antwort ein wenig mehr Details bringen kann.

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

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

ist völlig äquivalent zu:

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

Wenn Sie wollten, könnten Sie setzen using Richtlinien auf allen diesen Ebenen. (Natürlich wollen wir haben usings nur an einem Ort, aber es wäre legal nach der Sprache.)

Die Regel für die Auflösung, welcher Typ impliziert wird, kann wie folgt formuliert werden: Suchen Sie zuerst den innersten "Geltungsbereich" für eine Übereinstimmung, wenn dort nichts gefunden wird, gehen Sie eine Ebene weiter zum nächsten Bereich und suchen Sie dort, und so weiter, bis ein Treffer gefunden wurde. Wenn auf einer Ebene mehr als eine Übereinstimmung gefunden wird, wählen Sie diese aus und geben eine Compiler-Warnung aus, wenn einer der Typen aus der aktuellen Assembly stammt. Ansonsten, geben Sie auf (Kompilierungsfehler).

Lassen Sie uns nun explizit erläutern, was dies in einem konkreten Beispiel mit den beiden wichtigsten Konventionen bedeutet.

(1) mit usings draußen:

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

Im obigen Fall, um herauszufinden, welcher Typ Ambiguous ist, die Suche geht in dieser Reihenfolge:

  1. Verschachtelte Typen im Inneren C (einschließlich vererbte verschachtelte Typen)
  2. Gibt den aktuellen Namespace ein MyCorp.TheProduct.SomeModule.Utilities
  3. Gibt den Namespace ein MyCorp.TheProduct.SomeModule
  4. Typen ein MyCorp.TheProduct
  5. Typen ein MyCorp
  6. Typen in der Null Namespace (der globale Namespace)
  7. Typen ein System, System.Collections.Generic, System.Linq, MyCorp.TheProduct.OtherModule, MyCorp.TheProduct.OtherModule.Integration, und ThirdParty

Die andere Konvention:

(2) Mit using innen:

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 geht in dieser Reihenfolge:

  1. Verschachtelte Typen im Inneren C (einschließlich vererbte verschachtelte Typen)
  2. Gibt den aktuellen Namespace ein MyCorp.TheProduct.SomeModule.Utilities
  3. Typen ein System, System.Collections.Generic, System.Linq, MyCorp.TheProduct, MyCorp.TheProduct.OtherModule, MyCorp.TheProduct.OtherModule.Integration, und ThirdParty
  4. Gibt den Namespace ein MyCorp.TheProduct.SomeModule
  5. Typen ein MyCorp
  6. Typen in der Null Namespace (der globale Namespace)

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

Abschließende Bemerkungen

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

Wenn ein verschachtelter Namespace denselben Namen wie ein Typ hat, kann dies ebenfalls Probleme verursachen.

Es ist immer gefährlich, die Verwendung von einem Ort zu einem anderen zu verschieben, da sich die Suchhierarchie ändert und ein anderer Typ gefunden werden kann. Wählen Sie deshalb eine Konvention und bleiben Sie dabei, so dass Sie den Einsatz niemals verschieben müssen.

Die Vorlagen von Visual Studio legen standardmäßig die Usings fest draußen des Namespace (wenn Sie beispielsweise VS veranlassen, eine neue Klasse in einer neuen Datei zu generieren).

Ein (winziger) Vorteil des Gebrauchs draußen Sie können dann beispielsweise die using-Direktiven für ein globales Attribut verwenden [assembly: ComVisible(false)] Anstatt von [assembly: System.Runtime.InteropServices.ComVisible(false)].


345
2018-04-18 21:00



Wenn Sie ihn in die Namespaces einfügen, werden die Deklarationen lokal für diesen Namespace für die Datei verwendet (falls Sie mehrere Namespaces in der Datei haben), aber wenn Sie nur einen Namespace pro Datei haben, macht es keinen großen Unterschied, ob sie nach draußen gehen oder 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;
}

178
2017-09-24 03:52



Gemäß Hanselman - Verwendung von Richtlinie und Montage Loading ... und andere solche Artikel gibt es technisch keinen Unterschied.

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


56
2017-09-24 03:53



Laut StyleCop-Dokumentation:

SA1200: Verwenden von directivesMustBePlacedWithinNamespace

Ursache Eine C # -Befehlsanweisung 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 nicht zu 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 Compilerfehler. Es ist jedoch unklar, welche Version des Guid-Typs zugewiesen wird. Wenn die Anweisung using innerhalb des Namespace verschoben wird (siehe unten), tritt ein Compilerfehler 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 enthält Guid g = new Guid("hello"); 

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 System.Guid-Typ namens Guid und erstellt außerdem einen eigenen Typ namens GUID mit einer übereinstimmenden Konstruktorschnittstelle. Später erstellt der Code eine Instanz des Typs Guid. Um diese Instanz zu erstellen, muss der Compiler zwischen den zwei verschiedenen Definitionen von Guid wählen. Wenn die Alias-Direktive using außerhalb des Namespace-Elements platziert wird, wählt der Compiler die lokale Definition von Guid, die im lokalen Namespace definiert ist, und ignoriert die alias-Anweisung, die außerhalb des Namespace definiert ist. Dies ist leider beim Lesen des Codes nicht offensichtlich.

Wenn die Alias-Anweisung using innerhalb des Namespace positioniert ist, muss der Compiler jedoch zwischen zwei verschiedenen konfliktiven Guid-Typen wählen, die beide im selben Namespace definiert sind. Beide Typen bieten einen passenden Konstruktor. Der Compiler kann keine Entscheidung treffen, daher kennzeichnet er den Compilerfehler.

Es ist eine schlechte Übung, die Alias-Anweisung using außerhalb des Namespace zu platzieren, da dies in Situationen wie dieser, in denen nicht offensichtlich ist, welche Version des Typs tatsächlich verwendet wird, zu Verwirrung führen kann. Dies kann möglicherweise zu einem Fehler führen, der schwer zu diagnostizieren ist.

Durch die Verwendung von Alias-Anweisungen innerhalb des Namespace-Elements wird dies als Fehlerquelle beseitigt.

  1. Mehrere Namespaces

Das Platzieren mehrerer Namespace-Elemente in einer einzelnen Datei ist im Allgemeinen eine schlechte Idee. Wenn dies jedoch getan wird, empfiehlt es sich, alle using-Direktiven in jedem Namespace-Element und nicht global am Anfang der Datei zu platzieren. Dies wird die Namespaces eng umgrenzen und dazu beitragen, das oben beschriebene Verhalten zu vermeiden.

Es ist wichtig zu beachten, dass, wenn Code mit Direktiven außerhalb des Namespace geschrieben wurde, beim Verschieben dieser Direktiven im Namespace darauf geachtet werden muss, dass dies die Semantik des Codes nicht ändert. Wie oben erläutert, ermöglicht es das Platzieren von Alias-Anweisungen innerhalb des Namespace-Elements dem Compiler, zwischen in Konflikt stehenden Typen auf eine Weise zu wählen, die nicht auftritt, wenn die Anweisungen außerhalb des Namespace platziert werden.

Wie man Verstöße beseitigt Um einen Verstoß gegen diese Regel zu beheben, verschieben Sie alle using-Anweisungen und alias-Anweisungen innerhalb des Namespace-Elements.


45
2017-09-14 15:17



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 Aussagen und muss voll 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 langatmigen 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:

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.


29
2017-10-10 18:47



Als Jeppe Stig Nielsen sagte, dieser Thread hat schon gute Antworten, aber ich dachte, dass diese offensichtliche Subtilität auch erwähnenswert ist.

using Direktiven, die in Namespaces angegeben sind, können zu einem kürzeren Code führen, da sie nicht vollständig qualifiziert sein müssen, wenn sie extern angegeben werden.

Das folgende Beispiel funktioniert wegen der Typen Foo und Bar sind beide im selben globalen Namespace, Outer.

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 dem auslassen using Richtlinie, kurz:

namespace Outer
{
    using Inner;

    class Bar
    {
        public Foo foo;
    }
}

2
2017-09-17 10:32



Die technischen Gründe werden in den Antworten diskutiert und ich denke, dass es am Ende zu den persönlichen Vorlieben kommt, da der Unterschied nicht so ist groß und es gibt Kompromisse für beide. Die Standardvorlage von Visual Studio zum Erstellen .csDateien verwenden using Anweisungen außerhalb von Namespaces, z.

Man kann stylicop überprüfen lassen using Anweisungen außerhalb von Namespaces durch Hinzufügen stylecop.json Datei im Stamm der Projektdatei mit den folgenden:

{
  "$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 Ihren Projekten als "vorhandene Verknüpfungsdatei" hinzufügen, um die Konfiguration auch für alle Ihre Projekte freizugeben.


0
2018-06-03 12:38



Es ist eine bessere Praxis, wenn diese Standard unter Verwendung von z.B.Verweise"in Ihrer Quelllösung verwendet werden sollte außerhalb der Namespaces und diejenigen, die sind "neue hinzugefügte Referenz" ist eine gute Übung, sollten Sie es in den Namespace setzen. Dies ist zu unterscheiden, welche Referenzen hinzugefügt werden.


-7
2017-10-14 21:30