Чи повинні директиви "використання" знаходитися всередині або поза простором імен?


2060

Я запускаю StyleCop над деяким кодом C #, і він постійно повідомляє, що мої usingдирективи повинні знаходитися всередині простору імен.

Чи є технічна причина для введення usingдиректив всередину, а не за межами простору імен?


4
Іноді це робить різницю , коли ви поклали usings: stackoverflow.com/questions/292535/linq-to-sql-designer-bug
Gius

82
Для довідки, існують наслідки, окрім питання про декілька класів на файл, тому якщо ви новачок у цьому питанні, будь ласка, читайте.
Чарлі

3
@ user-12506 - це не дуже добре для середньої та великої команди розробників, де потрібен певний рівень узгодженості коду. І як зазначалося раніше, якщо ви не розумієте різних макетів, ви можете виявити крайові корпуси, які працюють не так, як ви очікували.
benPearce

34
Термінологія: це не using твердження ; вони є using директивами . З usingіншого боку, оператор - це мовна структура, яка виникає разом з іншими висловлюваннями всередині методу тощо. Наприклад, using (var e = s.GetEnumerator()) { /* ... */ }висловлювання, яке є таким же, як і var e = s.GetEnumerator(); try { /* ... */ } finally { if (e != null) { e.Dispose(); } }.
Джеппе Стіг Нільсен

1
Якщо це вже ніхто не згадував, насправді Microsoft теж рекомендує вводити usingзаяви всередину namespaceдекларацій у свої внутрішні правила кодування
user1451111

Відповіді:


2132

Насправді є (тонка) різниця між ними. Уявіть, що у File1.cs є такий код:

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

Тепер уявіть, що хтось додає ще один файл (File2.cs) до проекту, який виглядає приблизно так:

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

Компілятор здійснює пошук, Outerперш ніж шукати ці usingдирективи поза простором імен, тому він знаходить Outer.Mathзамість цього System.Math. На жаль (або, на щастя?), Outer.MathВін не має PIучасника, тому File1 зараз зламаний.

Це змінюється, якщо ви помістите usingвсередину декларації простору імен так:

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

Тепер компілятор шукає Systemперед пошуком Outer, знаходить System.Math, і все добре.

Дехто вважає, що це Mathможе бути невірним іменем для визначеного користувачем класу, оскільки він вже є System; Справа тут лише в тому, що є різниця, і це впливає на збереження вашого коду.

Також цікаво відзначити, що станеться, якщо Fooне в просторі імен Outer, а не Outer.Inner. У цьому випадку додавання Outer.Mathу File2 розбиває File1 незалежно від того, куди usingйде. Це означає, що компілятор здійснює пошук у найпотужнішому просторі імен, перш ніж він перегляне будь-яку usingдирективу.


28
Це набагато краща причина застосувати оператори локально, ніж аргумент Марка з декількома просторами імен в одному файлі. Особливо обов'язково компілятор може і скаржиться на зіткнення іменування (див. Документацію StyleCop для цього правила (наприклад, як розміщено Джаредом)).
Девід Шмітт

148
Прийнята відповідь хороша, але мені здається, що це вагома причина для розміщення використовуючих пропозицій поза простором імен. Якщо я перебуваю в просторі імен Outer.Inner, я б очікував, що він буде використовувати клас Math від Outer.Inner, а не System.Math.
Френк Уолліс

7
Я погоджуюся і з цим. Прийнята відповідь правильна тим, що вона технічно описує різницю. Однак одному або іншому класу знадобиться явне опитування. Я б дуже швидко, щоб "Math" вирішив мій власний локальний клас, а "System.Math" посилався на зовнішній клас - навіть якщо System.Math використовувався як "Math" до існування Outer.Math. Так, більше роботи над виправленням багатьох попередніх посилань, але це також може бути натяком на те, що, можливо, Outer.Math повинен мати іншу назву!
mbmcavoy

13
Чудова відповідь, але мені здається, що я хотів би лише поставити не-рамки, використовуючи оператори на місцевому рівні, і зберегти рамки, використовуючи заяви глобальні. У когось є додаткові пояснення, чому я повинен повністю змінити свої уподобання? Також звідки це взялося, шаблони в VS2008 розміщували за межами простору імен?
Тимін

31
Я думаю, що це скоріше погана умова іменування, а не зміна місця використання. У вашому рішенні не повинно бути класу під назвою Math
jDeveloper

454

У цій темі вже є кілька чудових відповідей, але я думаю, що я можу додати трохи детальніше з цією додатковою відповіддю.

Спочатку пам’ятайте, що декларація простору імен з періодами, як-от:

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

повністю еквівалентний:

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

Якщо ви цього хочете, ви можете вводити usingдирективи на всі ці рівні. (Звичайно, ми хочемо матиusing s лише в одному місці, але це було б законно відповідно до мови.)

Правило для вирішення того, який тип мається на увазі, може бути вільно викладене так: Спочатку шукайте найбільш "внутрішній" сферу для відповідності, якщо там нічого не знайдено, вийдіть на один рівень до наступного обсягу та шукайте там тощо. , поки не буде знайдено відповідність Якщо на якомусь рівні виявлено більше одного збігу, якщо один із типів є з поточної збірки, виберіть його та видайте попередження компілятора. В іншому випадку відмовтеся (помилка часу компіляції).

Тепер давайте будемо чітко пояснювати, що це означає на конкретному прикладі з двома основними умовами.

(1) З уставками зовні:

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

У наведеному вище випадку, щоб дізнатися, що це за тип Ambiguous, пошук йде в такому порядку:

  1. Вкладені типи всередині C(включаючи успадковані вкладені типи)
  2. Типи в поточному просторі імен MyCorp.TheProduct.SomeModule.Utilities
  3. Типи в просторі імен MyCorp.TheProduct.SomeModule
  4. Типи в MyCorp.TheProduct
  5. Типи в MyCorp
  6. Типи в нульовій області імен (глобальна область імен)
  7. Типи в System, System.Collections.Generic, System.Linq, MyCorp.TheProduct.OtherModule, MyCorp.TheProduct.OtherModule.Integration, іThirdParty

Інша конвенція:

(2) З вставками всередині:

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

Тепер пошук типу Ambiguousйде в такому порядку:

  1. Вкладені типи всередині C(включаючи успадковані вкладені типи)
  2. Типи в поточному просторі імен MyCorp.TheProduct.SomeModule.Utilities
  3. Типи в System, System.Collections.Generic, System.Linq, MyCorp.TheProduct, MyCorp.TheProduct.OtherModule, MyCorp.TheProduct.OtherModule.Integration, іThirdParty
  4. Типи в просторі імен MyCorp.TheProduct.SomeModule
  5. Типи в MyCorp
  6. Типи в нульовій області імен (глобальна область імен)

(Зауважте, що MyCorp.TheProductвін входив до "3." і тому не потрібен був між "4." і "5.".)

Прикінцеві зауваження

Незалежно від того, розміщуєте ви всередині або поза декларацією простору імен, завжди є можливість, щоб хтось пізніше додав новий тип з однаковим іменем до одного з просторів імен, які мають більший пріоритет.

Крім того, якщо вкладений простір імен має те саме ім’я, що і тип, це може спричинити проблеми.

Завжди небезпечно переміщувати вставки з одного місця в інше, оскільки змінюється ієрархія пошуку, і може бути знайдений інший тип. Тому виберіть одну конвенцію і дотримуйтесь її, щоб вам не довелося ніколи переміщувати ваги.

Шаблони Visual Studio за замовчуванням розміщують вставки поза простором імен (наприклад, якщо ви змушуєте VS генерувати новий клас у новому файлі).

Однією (крихітною) перевагою використання поза межами є те, що ви можете використовувати директиви використання, наприклад, для глобального атрибута, [assembly: ComVisible(false)]а не для [assembly: System.Runtime.InteropServices.ComVisible(false)].


46
Це найкраще пояснення, оскільки воно підкреслює той факт, що позиція висловлювачів "використовуючого" - це навмисне рішення розробника. Ні в якому разі хтось не повинен недбало змінювати розташування висловлювань, що 'використовують', не розуміючи наслідків. Тому правило StyleCop просто німе.
ZunTzu

194

Якщо розмістити його всередині просторів імен, то декларації будуть локальними для цього простору імен для файлу (якщо у файлі є кілька просторів імен), але якщо у вас є лише одне простір імен на файл, це не має великої різниці, виходять вони за межі або всередині простору імен.

using ThisNamespace.IsImported.InAllNamespaces.Here;

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

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

namespace Namespace3
{ 
   using ThisNamespace.IsImported.InJustNamespace3;
}

простори імен забезпечують логічне розділення, а не фізичне (файлове).
Джовен

9
Не зовсім вірно, що різниці немає; usingдирективи всередині namespaceблоків можуть посилатися на відносні простори імен на основі namespaceблоку, що додається.
АБО Mapper

70
так, я знаю. ми встановили, що на це питання прийняли відповідь п’ять років тому.
Марк Сідаде

59

За словами Ханзельмана - Використання Директиви та завантаження Асамблеї ... та інших подібних статей технічно немає різниці.

Моя перевага - розмістити їх поза просторами імен.


3
@Chris M: е-е ... посилання, яке розміщено у відповіді, вказує на те, що немає користі в порівнянні з, фактично показуючи приклад, який фальсифікує претензію, зроблену у посиланні, яке ви опублікували ...
johnny,

2
Так, я не повністю прочитав цю тему, але купив, коли MVP сказали, що це правильно. Хлопець спростовує це, пояснює це і показує свій код далі вниз ... "ІЛ, який створює компілятор C #, в будь-якому випадку однаковий. Насправді компілятор C # генерує точно нічого, що відповідає кожному використанню директиви. Використання директив - це суто C # ism, і вони не мають значення для самого .NET. (Неправда для використання операторів, але це щось зовсім інше.) " Groups.google.com/group/wpf-disciples/msg/781738deb0a15c46
Chris McKee

84
Будь ласка, включіть резюме посилання. Коли зв'язок порушується (бо це буде відбуватися, достатньо часу), раптом відповідь 32 upvotes тільки варто My style is to put them outside the namespaces.- навряд чи відповідь на все.
ANeves

11
Ствердження тут просто неправильне ... є технічна різниця, і ваше власне цитування говорить так ... насправді, це все. Видаліть цю помилкову відповідь ... Є набагато кращі та точніші.
Джим Балтер

53

Відповідно до Документації StyleCop:

SA1200: ВикористанняDirectivesMustBePlacedWithinNamespace

Причина AC # за допомогою директиви розміщується поза елементом простору імен.

Опис правила Порушення цього правила відбувається, коли використовуюча директива або директива використання псевдоніма розміщується поза елементом простору імен, якщо тільки файл не містить елементів простору імен.

Наприклад, наступний код призведе до двох порушень цього правила.

using System;
using Guid = System.Guid;

namespace Microsoft.Sample
{
    public class Program
    {
    }
}

Наступний код, однак, не призведе до порушень цього правила:

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

    public class Program
    {
    }
}

Цей код буде складено чисто, без помилок компілятора. Однак незрозуміло, яка версія типу Guid виділяється. Якщо директива використання буде переміщена всередині простору імен, як показано нижче, станеться помилка компілятора:

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

Код не вдається в наступній помилці компілятора, знайденій у рядку, що містить Guid g = new Guid("hello");

CS0576: Простір імен "Microsoft.Sample" містить визначення, що суперечить псевдоніму "Guid"

Код створює псевдонім типу System.Guid під назвою Guid, а також створює власний тип під назвою Guid з інтерфейсом відповідного конструктора. Пізніше код створює екземпляр типу Guid. Щоб створити цей екземпляр, компілятор повинен вибрати між двома різними визначеннями Guid. Коли директива використання псевдоніму розміщується поза елементом простору імен, компілятор вибере локальне визначення Guid, визначене в локальному просторі імен, і повністю ігнорує директиву використання псевдоніма, визначену поза простором імен. Це, на жаль, не очевидно при читанні коду.

Однак, коли директива використання псевдоніму розташована в просторі імен, компілятору доводиться вибирати між двома різними конфліктуючими типами Guid, визначеними в одній просторі імен. Обидва ці типи забезпечують відповідний конструктор. Компілятор не може прийняти рішення, тому він позначає помилку компілятора.

Розміщення директиви використання псевдоніму поза простором імен є поганою практикою, оскільки це може призвести до плутанини в таких ситуаціях, коли не очевидно, яка версія типу використовується насправді. Це потенційно може призвести до помилок, які можуть бути важко діагностувати.

Якщо розміщувати директиви-псевдоніми в елементі простору імен, це усуває це як джерело помилок.

  1. Кілька просторів імен

Розміщення декількох елементів простору імен в одному файлі, як правило, погана ідея, але якщо і коли це зроблено, то добре використовувати всі директиви, що використовують, в межах кожного з елементів простору імен, а не глобально вгорі файлу. Це буде чітко охоплювати простори імен, а також допоможе уникнути виду поведінки, описаного вище.

Важливо зазначити, що коли код був написаний за допомогою директив, розміщених поза простором імен, слід бути обережним при переміщенні цих директив у просторі імен, щоб переконатися, що це не змінює семантику коду. Як пояснено вище, розміщення директив з використанням псевдоніму в елементі простору імен дозволяє компілятору вибирати між конфліктуючими типами способами, які не відбудуться, якщо директиви розміщуються поза простором імен.

Як виправити порушення Щоб виправити порушення цього правила, перемістіть усі, використовуючи директиви та директиви-псевдоніми в межах простору імен.


1
@Jared - як я зазначив у своїй відповіді, моїм кращим способом вирішення є лише один клас на файл. Я думаю, що це досить поширена умова.
benPearce

24
Дійсно, це також правило StyleCop! SA1402: Документ AC # може містити лише один клас на рівні кореня, якщо всі класи часткові та не мають одного типу. Демонструючи одне правило, порушуючи інше, просто капає неправильний соус.
Завдання

6
Запропонований тим, що це перша відповідь, яка фактично висвітлює його з точки зору StyleCop. Особисто мені подобається візуальне відчуття usings за межами простору імен. Внутрішня usings виглядає на мене так потворно. :)
nawfal

2
Нарешті гарна відповідь на запитання. І коментар benPearce не має значення ... це не має нічого спільного з кількістю класів у файлі.
Джим Балтер

35

Виникає проблема з розміщенням операторів у просторі імен, коли ви хочете використовувати псевдоніми. Псевдонім не отримує користі від попередніх usingтверджень і повинен бути повністю кваліфікований.

Поміркуйте:

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

    class MyClass
    {
    }
}

проти:

using System;

namespace MyNamespace
{
    using MyAlias = DateTime;

    class MyClass
    {
    }
}

Це може бути особливо вираженим, якщо у вас є довготривалий псевдонім, такий як наступний (саме так я знайшов проблему):

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

З usingоператорами всередині простору імен він раптом стає:

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

Не дуже.


1
Вам classпотрібно ім'я (ідентифікатор). Ви не можете мати usingдирективи всередині класу, як ви вказуєте. Він повинен знаходитись на рівні простору імен, наприклад, поза зовнішнім namespace, або просто всередині внутрішнього namespace(але не всередині класу / інтерфейсу / тощо).
Jeppe Stig Nielsen

@JeppeStigNielsen Дякую Я usingпомилково поставив директиви. Я відредагував це, як я мав намір це зробити. Дякуємо, що вказали. Однак міркування все ж такі.
Нео

4

Як Джеппе Стіг Нільсен сказав , ця тема вже має чудові відповіді, але я вважав, що ця досить очевидна тонкість також варто згадати.

using директиви, визначені у просторах імен, можуть створювати коротший код, оскільки їм не потрібно бути повністю кваліфікованими, як коли вони вказані зовні.

Наступний приклад працює , так як типи Fooі Barобидва знаходяться в одній і тій же глобальний простір імен, Outer.

Припустимо, файл коду Foo.cs :

namespace Outer.Inner
{
    class Foo { }
}

І Bar.cs :

namespace Outer
{
    using Outer.Inner;

    class Bar
    {
        public Foo foo;
    }
}

Це може опустити зовнішню область імен у usingдирективі, коротко:

namespace Outer
{
    using Inner;

    class Bar
    {
        public Foo foo;
    }
}

8
Це правда, що ви "можете опустити зовнішню область імен", але це не означає, що вам слід. Для мене це ще один аргумент того, чому використання директив (крім псевдонімів, як у відповіді @ Neo) слід виходити за межі простору імен, щоб змусити повноцінно назви простору імен.
Кіт Робертсон

4

На одну зморшку я зіткнувся (що не описано в інших відповідях):

Припустимо, у вас є ці простори імен:

  • Щось.Інше
  • Батько. Щось. Інше

При використанні using Something.Other зовні від аnamespace Parent , це відноситься до першого (Something.Other).

Однак якщо ви використовуєте його всередині цієї декларації простору імен, воно посилається на друге (Parent.Something.Other)!

Є просте рішення: додайте global::префікс " ": docs

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

2

Технічні причини обговорюються у відповідях, і я вважаю, що мова йде про особисті переваги врешті-решт, оскільки різниця не така велика і для них обох є компроміси. Шаблон Visual Studio за замовчуванням для створення .csфайлів використовує usingдирективи поза просторами імен, наприклад

Можна налаштувати stylecop для перевірки usingдиректив поза просторами імен, додавши stylecop.jsonфайл у корінь файлу проекту із наступним:

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

Ви можете створити цей конфігураційний файл на рівні рішення та додати його до своїх проектів як "Існуючий файл посилань", щоб поділитися конфігурацією також у всіх ваших проектах.


2

Ще одна тонкість, на яку я не вірю, була охоплена іншими відповідями - це коли ви маєте клас та простір імен з тим самим іменем.

Якщо у вас імпорт всередині простору імен, він знайде клас. Якщо імпорт знаходиться за межами простору імен, імпорт буде ігноруватися, а клас та простір імен повинні бути повністю кваліфіковані.

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

-8

Це краща практика, якщо ті, що використовують за замовчуванням, тобто " посилання ", що використовуються у вихідному рішенні, повинні знаходитись поза просторами імен, а ті, що є "новою доданою посиланням", є хорошою практикою. Це полягає в тому, щоб розрізнити, які посилання додаються.


6
Ні, насправді це погана ідея. Не слід базувати розташування між локальним та глобальним обсягами використання директив на тому, що вони додані чи ні. Натомість, є хорошою практикою їх алфавітувати, за винятком посилань BCL, які мають бути згори.
Абель
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.