Czy dyrektywy 'using' powinny znajdować się wewnątrz czy poza przestrzenią nazw?

Używałem StyleCop nad jakimś kodem C# i ciągle donosi, że moje dyrektywy using powinny znajdować się w przestrzeni nazw.

Czy istnieje techniczny powód umieszczania using dyrektyw wewnątrz zamiast poza przestrzenią nazw?

Author: Philippe, 2008-09-24

12 answers

Istnieje rzeczywiście (subtelna) różnica między tymi dwoma. Wyobraź sobie, że masz następujący kod w Pliku1.cs:

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

Teraz wyobraź sobie, że ktoś dodaje inny plik (File2.cs) do projektu, który wygląda tak:

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

Kompilator przeszukuje Outer przed przejrzeniem tych using dyrektyw poza przestrzenią nazw, więc Znajduje Outer.Math zamiast System.Math. Niestety (a może na szczęście?), Outer.Math nie ma PI członka, więc File1 jest teraz zepsuty.

To się zmieni jeśli umieść using wewnątrz deklaracji przestrzeni nazw, w następujący sposób:

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

Teraz kompilator przeszukuje System przed przeszukaniem Outer, znajduje System.Math i wszystko jest dobrze.

Niektórzy twierdzą, że Math może być złą nazwą dla klasy zdefiniowanej przez użytkownika, ponieważ istnieje już jedna w System; chodzi o to, że tam jest różnicą, która wpływa na konserwowalność Twojego kodu.

Warto również zauważyć, co się stanie, jeśli Foo znajduje się w przestrzeni nazw Outer, zamiast Outer.Inner. W takim przypadku dodanie Outer.Math w Pliku2 powoduje przerwanie Pliku1 bez względu na to, gdzie idzie using. Oznacza to, że kompilator przeszukuje wewnętrzną przestrzeń nazw, zanim przejrzy jakąkolwiek dyrektywę using.

 2198
Author: Charlie,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-13 11:46:23

Ten wątek ma już kilka świetnych odpowiedzi, ale czuję, że mogę wnieść trochę więcej szczegółów z tej dodatkowej odpowiedzi.

Po pierwsze, pamiętaj, że deklaracja przestrzeni nazw z kropkami, jak:

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

Jest całkowicie równoważne:

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

Jeśli chcesz, możesz umieścić using dyrektywy na wszystkich tych poziomach. (Oczywiście, chcemy mieć using s tylko w jednym miejscu, ale byłoby to zgodne z prawem w zależności od języka.)

Reguła rozwiązywania, który typ jest można to luźno określić w następujący sposób: najpierw przeszukaj najbardziej wewnętrzny "zakres" dla dopasowania, jeśli nic tam nie zostanie znalezione, idź jeden poziom do następnego zakresu i szukaj tam, i tak dalej , aż do znalezienia dopasowania. Jeśli na pewnym poziomie znajdzie się więcej niż jedno dopasowanie, Jeśli jeden z typów pochodzi z bieżącego złożenia, wybierz ten i wydaj Ostrzeżenie kompilatora. W przeciwnym razie Zrezygnuj(błąd czasu kompilacji).

Teraz, bądźmy jasne, co to oznacza w konkretnym przykładzie z dwóch głównych konwencje.

(1) z użyciem Na Zewnątrz:

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

W powyższym przypadku, aby dowiedzieć się, jaki jest typ Ambiguous, wyszukiwanie odbywa się w następującej kolejności:

  1. typy zagnieżdżone wewnątrz C (w tym dziedziczone typy zagnieżdżone)
  2. typy w bieżącej przestrzeni nazw MyCorp.TheProduct.SomeModule.Utilities
  3. typy w przestrzeni nazw MyCorp.TheProduct.SomeModule
  4. typy w MyCorp.TheProduct
  5. typy w MyCorp
  6. typy w null namespace (globalna przestrzeń nazw)
  7. typy w System, System.Collections.Generic, System.Linq, MyCorp.TheProduct.OtherModule, MyCorp.TheProduct.OtherModule.Integration, oraz ThirdParty

Druga konwencja:

(2) z użyciem wewnątrz:

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

Teraz Szukaj typu Ambiguous idzie w następującej kolejności:

  1. typy zagnieżdżone wewnątrz C (w tym dziedziczone typy zagnieżdżone)
  2. typy w bieżącej przestrzeni nazw MyCorp.TheProduct.SomeModule.Utilities
  3. typy w System, System.Collections.Generic, System.Linq, MyCorp.TheProduct, MyCorp.TheProduct.OtherModule, MyCorp.TheProduct.OtherModule.Integration, oraz ThirdParty
  4. typy w przestrzeni nazw MyCorp.TheProduct.SomeModule
  5. typy w MyCorp
  6. typy w null namespace (globalna przestrzeń nazw)

(zauważ, że MyCorp.TheProduct była częścią "3."i dlatego nie było potrzebne między" 4."i" 5.".)

Uwagi końcowe

Bez względu na to, czy umieścisz użycie wewnątrz czy poza deklaracją przestrzeni nazw, zawsze istnieje możliwość, że ktoś później doda nowy typ o identycznej nazwie do jednej z przestrzeni nazw o wyższym priorytecie.

Również, jeśli zagnieżdżone przestrzeń nazw ma taką samą nazwę jak typ, może powodować problemy.

Zawsze niebezpieczne jest przenoszenie użytkowników z jednego miejsca do drugiego, ponieważ hierarchia wyszukiwania zmienia się i można znaleźć inny typ. Dlatego wybierz jedną konwencję i trzymaj się jej, abyś nigdy nie musiał się poruszać.

Szablony Visual Studio domyślnie umieszczają użycie poza przestrzeni nazw (na przykład, jeśli tworzysz VS nową klasę w Nowym pliku).

Jeden (maleńka) zaletą używania poza jest to, że możesz następnie użyć dyrektyw using dla globalnego atrybutu, na przykład [assembly: ComVisible(false)] zamiast [assembly: System.Runtime.InteropServices.ComVisible(false)].

 481
Author: Jeppe Stig Nielsen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-11-08 09:57:51

Umieszczenie go wewnątrz przestrzeni nazw sprawia, że deklaracje są lokalne dla tej przestrzeni nazw dla pliku (w przypadku, gdy masz wiele przestrzeni nazw w pliku), ale jeśli masz tylko jedną przestrzeń nazw na plik, to nie ma dużej różnicy, czy wychodzą na zewnątrz, czy wewnątrz przestrzeni nazw.

using ThisNamespace.IsImported.InAllNamespaces.Here;

namespace Namespace1
{ 
   using ThisNamespace.IsImported.InNamespace1.AndNamespace2;

   namespace Namespace2
   { 
      using ThisNamespace.IsImported.InJustNamespace2;
   }       
}

namespace Namespace3
{ 
   using ThisNamespace.IsImported.InJustNamespace3;
}
 196
Author: Mark Cidade,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-04-17 14:49:14

Zgodnie z Hanselman-Korzystanie z dyrektywy i obciążenia montażowego... i innych tego typu artykułów nie ma praktycznie żadnej różnicy.

Preferuję umieszczanie ich poza przestrzeniami nazw.

 59
Author: Quintin Robinson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-10-22 14:46:04

Zgodnie z dokumentacją StyleCop:

SA1200: UsingDirectivesMustBePlacedWithinnamespace

Przyczyna C# using directive jest umieszczony poza elementem przestrzeni nazw.

Opis Reguły Naruszenie tej reguły ma miejsce, gdy dyrektywa using lub dyrektywa using-alias są umieszczone poza elementem przestrzeni nazw, chyba że plik nie zawiera żadnych elementów przestrzeni nazw.

Na przykład poniższy kod spowodowałby dwa naruszenia tego zasada.

using System;
using Guid = System.Guid;

namespace Microsoft.Sample
{
    public class Program
    {
    }
}

Poniższy kodeks nie skutkowałby jednak naruszeniem tej zasady:

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

    public class Program
    {
    }
}

Ten kod będzie kompilowany czysto, bez żadnych błędów kompilatora. Nie jest jednak jasne, która wersja typu Guid jest przydzielana. Jeśli dyrektywa using zostanie przeniesiona wewnątrz przestrzeni nazw, jak pokazano poniżej, wystąpi błąd kompilatora:

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

Kod nie działa na poniższym błędzie kompilatora, znalezionym w linii zawierającej Guid g = new Guid("hello");

CS0576: Przestrzeń nazw "Microsoft.Przykład "zawiera definicję sprzeczną z aliasem "Guid"

Kod tworzy alias do systemu.Typ Guid o nazwie Guid, a także tworzy swój własny typ o nazwie Guid z dopasowanym interfejsem konstruktora. Później kod tworzy instancję typu Guid. Aby utworzyć tę instancję, kompilator musi wybrać jedną z dwóch różnych definicji Guid. Gdy dyrektywa using-alias zostanie umieszczona poza elementem przestrzeni nazw, kompilator wybierze definicję lokalną Guid zdefiniowanego w lokalnej przestrzeni nazw i całkowicie zignorować dyrektywę using-alias zdefiniowaną poza przestrzenią nazw. Niestety nie jest to oczywiste podczas czytania kodu.

Gdy dyrektywa using-alias jest umieszczona w przestrzeni nazw, kompilator musi wybrać pomiędzy dwoma różnymi, kolidującymi typami Guid zdefiniowanymi w tej samej przestrzeni nazw. Oba te typy zapewniają odpowiedni konstruktor. Kompilator nie jest w stanie podjąć decyzji, więc oznacza błąd kompilatora.

Umieszczanie dyrektywy using-alias poza przestrzenią nazw jest złą praktyką, ponieważ może prowadzić do nieporozumień w sytuacjach takich jak ta, gdzie nie jest oczywiste, która wersja typu jest faktycznie używana. Może to potencjalnie prowadzić do błędu, który może być trudny do zdiagnozowania.

Umieszczenie dyrektyw using-alias w elemencie przestrzeni nazw eliminuje to jako źródło błędów.

  1. Wiele Przestrzeni Nazw

Umieszczenie wielu Elementy przestrzeni nazw w pojedynczym pliku to zazwyczaj zły pomysł, ale jeśli i kiedy to się stanie, dobrym pomysłem jest umieszczenie wszystkich dyrektyw w każdym elemencie przestrzeni nazw, a nie globalnie na górze pliku. Spowoduje to ścisły zakres przestrzeni nazw, a także pomoże uniknąć zachowania opisanego powyżej.

Ważne jest, aby pamiętać, że gdy kod został napisany z użyciem dyrektyw umieszczonych poza przestrzenią nazw, należy zachować ostrożność podczas przenoszenia tych dyrektyw dyrektywy w przestrzeni nazw, aby upewnić się, że nie zmienia to semantyki kodu. Jak wyjaśniono powyżej, umieszczenie dyrektyw using-alias w elemencie przestrzeni nazw pozwala kompilatorowi na wybór pomiędzy przeciwstawnymi typami w sposób, który nie nastąpi, gdy dyrektywy zostaną umieszczone poza przestrzenią nazw.

Jak naprawić naruszenia Aby naprawić naruszenie tej reguły, Przenieś wszystkie używające dyrektyw i używające-aliasów dyrektywy wewnątrz elementu przestrzeni nazw.

 53
Author: JaredCacurak,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-09-14 15:17:10

Występuje problem z umieszczaniem instrukcji using wewnątrz przestrzeni nazw, gdy chcesz używać aliasów. Alias nie korzysta z wcześniejszych instrukcji using i musi być w pełni kwalifikowany.

Rozważmy:

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

    class MyClass
    {
    }
}

Kontra:

using System;

namespace MyNamespace
{
    using MyAlias = DateTime;

    class MyClass
    {
    }
}

Może to być szczególnie wymawiane, jeśli masz długi Alias, taki jak poniżej (tak znalazłem problem):

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

Z using poleceniami wewnątrz przestrzeni nazw, nagle staje się:

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

Nie Ładna.

 35
Author: Neo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-03-24 01:57:00

Jedna zmarszczka, na którą wpadłem (która nie jest pokryta innymi odpowiedziami):

Załóżmy, że masz te przestrzenie nazw:

    Coś.Inne Rodzic.Coś.Inne

Kiedy używasz using Something.Other poza {[16] } z namespace Parent odnosi się do pierwszego (coś.Inne).

Jeśli jednak użyjesz wewnątrz tej deklaracji przestrzeni nazw, odnosi się ona do drugiej (rodzica.Coś.Inne)!

Jest proste rozwiązanie: dodaj przedrostek" global::": docs

namespace Parent
{
   using global::Something.Other;
   // etc
}
 7
Author: Hans Kesting,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-06-05 09:25:22

Jak powiedział Jeppe Stig Nielsen , Ten wątek ma już świetne odpowiedzi, ale pomyślałem, że ta dość oczywista subtelność też była warta wspomnienia.

using dyrektywy określone wewnątrz przestrzeni nazw mogą tworzyć krótszy Kod, ponieważ nie muszą być w pełni kwalifikowane, jak wtedy, gdy są określone Na Zewnątrz.

Poniższy przykład działa, ponieważ typy Foo i {[5] } znajdują się w tej samej globalnej przestrzeni nazw, Outer.

Załóżmy, że plik kodu Foo.cs:

namespace Outer.Inner
{
    class Foo { }
}

I Bar.cs:

namespace Outer
{
    using Outer.Inner;

    class Bar
    {
        public Foo foo;
    }
}

, które mogą pomijać zewnętrzną przestrzeń nazw w dyrektywie using, w skrócie:

namespace Outer
{
    using Inner;

    class Bar
    {
        public Foo foo;
    }
}
 4
Author: Biscuits,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:26:32

Powołując się na wewnętrzne wytyczne Microsoftu, pamiętaj, że są one napisane przez kogoś, kto prawdopodobnie ma mniej niż dziesięć lat doświadczenia w kodowaniu. Innymi słowy, są prawdopodobnie oparte na niczym bardziej solidnym niż osobistych preferencjach. Szczególnie w czymś takim jak C# to Takie Nowe.

Z reguły zewnętrzne dyrektywy using (Na przykład Przestrzenie nazw systemu i Microsoft) powinny być umieszczone poza dyrektywą namespace. Są to wartości domyślne, które powinny być stosowane we wszystkich przypadkach o ile nie określono inaczej . Powinno to obejmować biblioteki wewnętrzne organizacji, które nie są częścią bieżącego projektu, lub dyrektywy using, które odwołują się do innych podstawowych przestrzeni nazw w tym samym projekcie. Wszelkie dyrektywy using odwołujące się do innych modułów w bieżącym projekcie i przestrzeni nazw powinny być umieszczone wewnątrz dyrektywy namespace. Służy to dwóm konkretnym funkcjom:

  • zapewnia wizualne rozróżnienie między modułami lokalnymi i "inne" moduły, czyli Wszystko inne.
  • obejmuje ona dyrektywy lokalne, które mają być stosowane preferencyjnie nad dyrektywami globalnymi.
Ten ostatni powód jest znaczący. Oznacza to, że trudniej jest wprowadzić niejednoznaczny problem odniesienia, który może być wprowadzony przez zmianę Nie większą niż refaktoryzujący kod. Oznacza to, że przenosisz metodę z jednego pliku do drugiego i nagle pojawia się błąd, którego wcześniej nie było. Potocznie, a "heisenbug" - historycznie diabelsko trudny do wyśledzenia.

Jako jeszcze bardziej ogólna zasada, dobrym do naśladowania jest to. Jeśli widzisz coś nieodłącznego dla języka, co wydaje się być bezużyteczną opcją, Załóżmy, że tak nie jest. W rzeczywistości im trudniej jest zrozumieć, dlaczego taka opcja istnieje, tym ważniejsze jest założenie, że jest. Wykonaj badania dotyczące konkretnych różnic między tymi dwoma opcjami, a następnie zastanów się długo i mocno nad konsekwencjami. Zazwyczaj znajdziesz niezwykle wnikliwe i sprytne rozwiązanie niejasnego problemu, który projektant języka umieścił tam specjalnie po to, aby ułatwić Ci życie. Bądź odpowiednio wdzięczny i skorzystaj z tego.

 4
Author: Yrth,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-07-31 13:48:21

Kolejna subtelność, w którą nie wierzę, została pokryta przez inne odpowiedzi, dotyczy sytuacji, gdy masz klasę i przestrzeń nazw o tej samej nazwie.

Gdy masz import wewnątrz przestrzeni nazw, to znajdzie klasę. Jeśli import znajduje się poza przestrzenią nazw, import zostanie zignorowany, a klasa i przestrzeń nazw muszą być w pełni kwalifikowane.

//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.
        }
    }
}
 3
Author: Ben Gardner,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-09-03 08:13:13

Przyczyny techniczne są omawiane w odpowiedziach i myślę, że w końcu chodzi o osobiste preferencje, ponieważ różnica nie jest taka duża i są kompromisy dla obu z nich. Domyślny szablon Visual Studio do tworzenia plików .cs używa dyrektyw using poza przestrzeniami nazw, np.

Można dostosować stylecop do sprawdzania dyrektyw using poza przestrzeniami nazw poprzez dodanie pliku stylecop.json do katalogu głównego projektu za pomocą po:

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

Możesz utworzyć ten plik konfiguracyjny na poziomie rozwiązania i dodać go do swoich projektów jako "istniejący plik łącza", aby udostępnić konfigurację we wszystkich swoich projektach.

 2
Author: sotn,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-06-03 12:38:41

Jest lepszą praktyką, jeśli te domyślne używanie np. "references " używane w Twoim źródłowym rozwiązaniu powinno znajdować się poza przestrzeniami nazw, a te, które są "new added reference" jest dobrą praktyką, jeśli powinieneś umieścić je wewnątrz przestrzeni nazw. Ma to na celu rozróżnienie, jakie odniesienia są dodawane.

 -8
Author: Israel Ocbina,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-10-14 21:37:07