Pytanie Czy dyrektywy "przy użyciu" powinny znajdować się w przestrzeni nazw lub poza nią?


Biegałem StyleCop przez jakiś kod C # i ciągle raportuje to moje using dyrektywy powinny znajdować się wewnątrz przestrzeni nazw.

Czy istnieje techniczny powód umieszczenia using dyrektywy wewnątrz zamiast przestrzeni nazw?


1741
2017-09-24 03:49


pochodzenie


Czasami robi różnicę w miejscu, w którym używasz: stackoverflow.com/questions/292535/linq-to-sql-designer-bug - gius
Tylko w celach informacyjnych, istnieją implikacje wykraczające poza kwestię wielu klas na plik, więc jeśli jesteś nowy na to pytanie, proszę czytaj dalej. - Charlie
@ user-12506 - to nie działa zbyt dobrze w średnim i dużym zespole programistów, gdzie wymagany jest pewien poziom spójności kodu. Jak już wspomniano wcześniej, jeśli nie rozumiesz różnych układów, możesz znaleźć przypadki skrajne, które nie działają zgodnie z oczekiwaniami. - benPearce
Terminologia: te nie są using  sprawozdania; oni są using  dyrektywy. ZA using oświadczenie, z drugiej strony, jest strukturą języka, która występuje wraz z innymi oświadczeniami wewnątrz ciała metody itp. Jako przykład, using (var e = s.GetEnumerator()) { /* ... */ } jest stwierdzeniem, które jest luźno takie samo jak var e = s.GetEnumerator(); try { /* ... */ } finally { if (e != null) { e.Dispose(); } }. - Jeppe Stig Nielsen
Jeśli nikomu o tym nie wspomniał, tak naprawdę Microsoft zaleca również umieszczanie using oświadczenia wewnątrz namespace deklaracje w ich wewnętrzne prowadnice kodu - user1451111


Odpowiedzi:


Istnieje rzeczywiście (subtelna) różnica między tymi dwoma. Wyobraź sobie, że masz następujący kod w File1.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 następująco:

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

Wyszukiwanie kompilatora Outer zanim się na nie spojrzy using dyrektywy poza obszarem 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 uszkodzony.

To się zmienia, jeśli umieścisz using wewnątrz deklaracji przestrzeni nazw, jak następuje:

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

Teraz kompilator wyszukuje System przed wyszukiwaniem Outer, znajduje System.Mathi wszystko jest dobrze.

Niektórzy twierdzą, że tak Math może być złą nazwą dla klasy zdefiniowanej przez użytkownika, ponieważ jest już jedna System; chodzi o to właśnie tutaj jest różnica i wpływa na łatwość obsługi twojego kodu.

Warto również zauważyć, co się stanie, jeśli Foo znajduje się w przestrzeni nazw Outerraczej niż Outer.Inner. W takim przypadku, dodawanie Outer.Math w File2 łamie File1 niezależnie od tego gdzie using idzie. Oznacza to, że kompilator przeszukuje najbardziej otaczającą przestrzeń nazw, zanim przejrzy jakąkolwiek using dyrektywa.


1844
2017-09-30 02:33



Jest to o wiele lepszy powód do używania lokalnie instrukcji niż argument Mark-wielu-przestrzeni-w-jednym pliku. Zwłaszcza sinus, który kompilacja może i będzie narzekać na konflikt nazw (zobacz dokumentację StyleCop dla tej reguły (na przykład opublikowaną przez Jareda)). - David Schmitt
Przyjęta odpowiedź jest dobra, ale dla mnie wydaje się dobrym powodem do wprowadzenia klauzul dotyczących używania na zewnątrz przestrzeń nazw. Jeśli jestem w przestrzeni nazw Outer.Inner, oczekiwałbym, że użyje klasy Math z Outer.Inner a nie System.Math. - Frank Wallis
Zgadzam się również z tym. Przyjęta odpowiedź jest poprawna, ponieważ technicznie opisuje różnicę. Jednak jedna lub druga klasa będzie wymagać jawnego objaśnienia. Bardzo chciałbym, aby mój "lokalny" klasa był "matematyczny", a "System.Math" odnosi się do zewnętrznej klasy - nawet jeśli System.Math był używany jako "Math" przed Outer.Math istniała. Tak, to więcej pracy, aby naprawić wiele wcześniej istniejących referencji, ale może to być również wskazówka, że ​​być może Outer.Math powinien mieć inną nazwę! - mbmcavoy
Świetna odpowiedź, ale wydaje mi się, że chciałbym tylko lokalnie używać instrukcji niezwiązanych z ramami i utrzymywać strukturę za pomocą instrukcji globalnych. Czy ktoś ma dalsze wyjaśnienie, dlaczego powinienem całkowicie zmienić moje preferencje? Skąd się to wzięło, szablony w VS2008 używają poza przestrzenią nazw? - Thymine
Myślę, że jest to raczej zła konwencja nazewnictwa niż zmiana miejsca używania. W twoim rozwiązaniu nie powinno być klasy o nazwie Matematyka - jDeveloper


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

Najpierw pamiętaj o deklaracji przestrzeni nazw zawierającej kropki, np .:

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

jest całkowicie równoważny z:

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

Jeśli chcesz, możesz to zrobić using dyrektywy na wszystkich tych poziomach. (Oczywiście, chcemy mieć usings tylko w jednym miejscu, ale byłoby zgodne z językiem.)

Reguła rozstrzygania, jaki typ jest domyślny, może być luźno określona w ten sposób: Najpierw przeszukaj najbardziej wewnętrzny "zasięg" meczu, jeśli nic nie znajdziesz, przejdź jeden poziom do następnego zakresu i wyszukaj go, i tak dalej, dopóki nie zostanie znaleziony mecz. Jeśli na pewnym poziomie zostanie znalezione więcej niż jedno dopasowanie, jeśli jeden z typów pochodzi z bieżącego zespołu, wybierz go i wyświetl ostrzeżenie kompilatora. W przeciwnym razie poddaj się (błąd kompilacji).

Teraz wyraźmy, co to oznacza w konkretnym przykładzie z dwiema głównymi konwencjami.

(1) Z wykorzystaniem 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 typ Ambiguous to wyszukiwanie odbywa się w następującej kolejności:

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

Druga konwencja:

(2) Z wykorzystaniem 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 wyszukaj typ Ambiguous przechodzi w tej kolejności:

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

(Zauważ, że MyCorp.TheProduct był 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żytek wewnątrz deklaracji przestrzeni nazw, czy poza nią, zawsze istnieje możliwość, że ktoś później doda nowy typ o identycznej nazwie do jednej z przestrzeni nazw o wyższym priorytecie.

Ponadto, jeśli zagnieżdżona przestrzeń nazw ma taką samą nazwę jak typ, może powodować problemy.

Zawsze jest niebezpieczne przenoszenie użytków z jednej lokalizacji do drugiej, 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 programu Visual Studio domyślnie umieszczają aplikacje na zewnątrz przestrzeni nazw (na przykład, jeśli VS wygeneruje nową klasę w nowym pliku).

Jedna (malutka) przewaga posiadania korzyści na zewnątrz jest to, że możesz na przykład wykorzystać dyrektywy dotyczące atrybutów globalnych [assembly: ComVisible(false)] zamiast [assembly: System.Runtime.InteropServices.ComVisible(false)].


346
2018-04-18 21:00



dzięki, jest to znacznie lepsze wyjaśnienie niż zaakceptowana odpowiedź. - thor_hayek
Jest to najlepsze wytłumaczenie, ponieważ podkreśla fakt, że pozycja oświadczeń "używanie" jest świadomą decyzją dewelopera. W żadnym wypadku nie należy bezmyślnie zmieniać położenia instrukcji "używanie" bez zrozumienia konsekwencji. Dlatego zasada StyleCop jest po prostu głupia. - ZunTzu


Umieszczenie go wewnątrz przestrzeni nazw powoduje, że deklaracje są lokalne dla tego obszaru nazw dla pliku (w przypadku, gdy w pliku znajduje się wiele przestrzeni nazw), ale jeśli w jednym pliku jest tylko jedna przestrzeń nazw, to nie ma znaczenia, czy wychodzą one na zewnątrz, czy też nie. 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;
}

178
2017-09-24 03:52



Przestrzenie nazw zapewniają separację logiczną, a nie fizyczną (plikową). - Jowen
To nie do końca prawda, że ​​nie ma różnicy; using dyrektywy w ramach namespace bloki mogą odnosić się do względnych przestrzeni nazw na podstawie otaczających namespace blok. - O. R. Mapper
tak, wiem. ustaliliśmy, że w odpowiedzi na to pytanie przyjęto pięć lat temu. - Mark Cidade


Według Hanselman - Używanie dyrektywy i montaż Ładowanie ... i inne takie artykuły nie ma żadnej różnicy technicznej.

Moją preferencją jest umieszczenie ich poza obszarami nazw.


56
2017-09-24 03:53



@ Chris M: uh ... link zamieszczony w odpowiedzi wskazuje, że jest Nie korzyści w stosunku do, poza tym, pokazując przykład, który fałszuje roszczenia zgłoszone w opublikowanym linku ... - johnny
Tak, nie w pełni przeczytałem wątek, ale kupiłem, kiedy MVPowie powiedzieli, że to prawda. Facet go obala, wyjaśnia i pokazuje swój kod dalej ... "IL, który generuje kompilator C #, jest taki sam w obu przypadkach: w rzeczywistości kompilator C # nie generuje dokładnie nic, co odpowiada każdej z dyrektyw używających. C # ism i nie mają żadnego znaczenia dla .NET (nie jest to prawdą w przypadku używania instrukcji, ale to coś zupełnie innego.) " groups.google.com/group/wpf-disciples/msg/781738deb0a15c46 - Chris McKee
Podsumuj link. Gdy link jest zepsuty (ponieważ to będzie zdarzyć się, biorąc pod uwagę wystarczająco dużo czasu), nagle odpowiedź z 32 przegranych jest tylko warta My style is to put them outside the namespaces. - ledwie odpowiedź. - ANeves
Twierdzenie tutaj jest po prostu błędne ... istnieje różnica techniczna i twoje własne cytowanie mówi tak ... w rzeczywistości, o to właśnie chodzi. Usuń tę błędną odpowiedź ... są znacznie lepsze i dokładniejsze. - Jim Balter


Zgodnie z dokumentacją StyleCop:

SA1200: UsingDirectivesMustBePlacedWithinNamespace

Przyczyna Dyrektywa C # using znajduje się poza elementem przestrzeni nazw.

Opis reguły Naruszenie tej reguły występuje, gdy dyrektywa using lub dyrektywa aliasów użycia znajduje się poza elementem przestrzeni nazw, chyba że plik nie zawiera żadnych elementów przestrzeni nazw.

Na przykład poniższy kod spowodowałby dwa naruszenia tej reguły.

using System;
using Guid = System.Guid;

namespace Microsoft.Sample
{
    public class Program
    {
    }
}

Poniższy kod nie spowoduje jednak naruszenia tej zasady:

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

    public class Program
    {
    }
}

Ten kod będzie się kompilował czysto, bez żadnych błędów kompilatora. Jednak nie jest jasne, która wersja typu Guid jest przydzielana. Jeśli dyrektywa use zostanie przeniesiona do 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 kończy się niepowodzeniem w przypadku następującego błędu kompilatora, znalezionego w wierszu zawierającym Guid g = new Guid("hello"); 

CS0576: Przestrzeń nazw "Microsoft.Przykład" zawiera definicję będącą w konflikcie z aliasem "Guid"

Kod tworzy alias do typu System.Guid o nazwie Guid, a także tworzy własny typ o nazwie Guid z pasującym interfejsem konstruktora. Później kod tworzy instancję typu Guid. Aby utworzyć to wystąpienie, kompilator musi wybrać między dwiema różnymi definicjami Guida. Jeśli dyrektywa aliasu używania znajduje się poza elementem przestrzeni nazw, kompilator wybierze lokalną definicję Guid zdefiniowaną w lokalnej przestrzeni nazw i całkowicie zignoruje dyrektywę aliasów użycia zdefiniowaną poza obszarem nazw. Niestety, nie jest to oczywiste podczas czytania kodu.

Kiedy jednak dyrektywa aliasu używania znajduje się w przestrzeni nazw, kompilator musi wybrać między dwoma różnymi, sprzecznymi typami Guid, zdefiniowanymi w obrębie tej samej przestrzeni nazw. Oba typy zapewniają odpowiedni konstruktor. Kompilator nie może podjąć decyzji, więc zgłasza błąd kompilatora.

Umieszczanie dyrektywy o użyciu aliasów poza obszarem nazw jest złą praktyką, ponieważ może prowadzić do zamieszania w takich sytuacjach, 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.

Umieszczanie dyrektyw aliasu użycia w elemencie przestrzeni nazw eliminuje to jako źródło błędów.

  1. Wiele przestrzeni nazw

Umieszczanie wielu elementów przestrzeni nazw w jednym pliku jest zazwyczaj złym pomysłem, ale jeśli to zrobimy, dobrze jest umieścić wszystkie za pomocą dyrektyw w każdym z elementów przestrzeni nazw, a nie globalnie u góry pliku. Spowoduje to dokładne dopasowanie przestrzeni nazw, a także pomoże uniknąć opisanego powyżej zachowania.

Ważne jest, aby pamiętać, że gdy kod został napisany przy użyciu dyrektyw umieszczonych poza przestrzenią nazw, należy zachować ostrożność podczas przenoszenia tych dyrektyw w obrębie przestrzeni nazw, aby upewnić się, że nie zmienia to semantyki kodu. Jak wyjaśniono powyżej, umieszczenie dyrektyw aliasu używania w elemencie przestrzeni nazw umożliwia kompilatorowi wybór między typami konfliktu w sposób, który nie nastąpi, gdy dyrektywy zostaną umieszczone poza obszarem nazw.

Jak naprawić naruszenia Aby naprawić naruszenie tej reguły, przenieś wszystkie za pomocą dyrektyw i dyrektyw aliasu używania w elemencie przestrzeni nazw.


45
2017-09-14 15:17



@Jared - jak zauważyłem w mojej odpowiedzi, moje preferowane obejście / rozwiązanie ma mieć tylko jedną klasę na plik. Myślę, że jest to dość powszechna konwencja. - benPearce
Rzeczywiście, jest to również zasada StyleCop! SA1402: Dokument C # może zawierać tylko jedną klasę na poziomie podstawowym, chyba że wszystkie klasy są częściowe i są tego samego typu. Pokazanie jednej reguły przez zerwanie kolejnej kropli tylko z niewłaściwym sosem. - Task
Przegłosowano za to, że jest pierwszą odpowiedzią, która faktycznie obejmuje go z perspektywy StyleCop. Osobiście lubię wrażenia wizualne usings poza przestrzenią nazw. Wewnętrzny usings wygląda dla mnie tak brzydko. :) - nawfal
Wreszcie dobra odpowiedź na to pytanie. Komentarz benPearce'a jest nieistotny ... nie ma to nic wspólnego z liczbą klas w teczce. - Jim Balter


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

Rozważać:

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

    class MyClass
    {
    }
}

przeciw:

using System;

namespace MyNamespace
{
    using MyAlias = DateTime;

    class MyClass
    {
    }
}

Może to być szczególnie wyraźne, jeśli masz długotrwały alias taki jak poniższy (w ten sposób znalazłem problem):

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

Z using w przestrzeni nazw staje się nagle:

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

Nie ładne.


29
2017-10-10 18:47



Twój class potrzebuje nazwy (identyfikatora). Nie możesz mieć using dyrektywę wewnątrz klasy, jak wskazujesz. Musi być na poziomie przestrzeni nazw, na przykład poza zewnętrznymi namespacelub tylko wewnątrz najbardziej wewnętrznej namespace (ale nie wewnątrz klasy / interfejsu / itp.). - Jeppe Stig Nielsen
@JeppeStigNielsen Dzięki. Zgubiłem using dyrektywy błędnie. Zmieniłem to tak, jak zamierzałem. Dzięki za wskazanie. Rozumowanie jest jednak wciąż takie samo. - Neo


Jako Jeppe Stig Nielsen powiedziany, ten wątek ma już świetne odpowiedzi, ale pomyślałem, że warto wspomnieć o tej dość oczywistej subtelności.

using dyrektywy określone w przestrzeniach nazw mogą zawierać krótszy kod, ponieważ nie muszą być w pełni kwalifikowane, ponieważ są określone na zewnątrz.

Poniższy przykład działa, ponieważ typy Foo i Bar są w tej samej globalnej przestrzeni nazw, Outer.

Podejmij plik kodu Foo.cs:

namespace Outer.Inner
{
    class Foo { }
}

I Bar.cs:

namespace Outer
{
    using Outer.Inner;

    class Bar
    {
        public Foo foo;
    }
}

To może ominąć zewnętrzną przestrzeń nazw w using dyrektywa, w skrócie:

namespace Outer
{
    using Inner;

    class Bar
    {
        public Foo foo;
    }
}

2
2017-09-17 10:32



To prawda, że ​​"możesz pominąć zewnętrzny obszar nazw", ale to nie znaczy, że powinieneś. Dla mnie jest to kolejny argument, dlaczego użycie dyrektyw (innych niż aliasy, jak w odpowiedzi @ Neo) powinno wyjść poza przestrzeń nazw, aby wymusić w pełni kwalifikowane nazwy przestrzeni nazw. - Keith Robertson