Должны ли директивы using находиться внутри или вне пространства имен?

2063

Я запускал StyleCop над кодом C #, и он продолжает сообщать, что мои usingдирективы должны быть внутри пространства имен.

Есть ли техническая причина для размещения usingдиректив внутри, а не вне пространства имен?

benPearce
источник
4
Иногда важно, где вы используете: stackoverflow.com/questions/292535/linq-to-sql-designer-bug
gius
82
Просто для справки, есть некоторые последствия, помимо вопроса о нескольких классах на файл, поэтому, если вы новичок в этом вопросе, пожалуйста, продолжайте читать.
Чарли
3
@ user-12506 - это не очень хорошо работает в средней и большой команде разработчиков, где требуется некоторый уровень согласованности кода. И, как отмечалось ранее, если вы не понимаете различные макеты, вы можете найти крайние случаи, которые не работают, как вы ожидаете.
benPearce
35
Терминология: это не using заявления ; они являются using директивами . С usingдругой стороны, оператор - это языковая структура, которая встречается вместе с другими операторами внутри тела метода и т. Д. В качестве примера можно привести using (var e = s.GetEnumerator()) { /* ... */ }оператор, который в значительной степени совпадает с var e = s.GetEnumerator(); try { /* ... */ } finally { if (e != null) { e.Dispose(); } }.
Джеппе Стиг Нильсен
1
Если об этом уже никто не упомянул, на самом деле Microsoft тоже рекомендует помещать usingоператоры в namespaceдекларации, в их внутренние
правила

Ответы:

2134

На самом деле есть (тонкая) разница между ними. Представьте, что у вас есть следующий код в 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 для этого правила (например, опубликованную Jared)).
Дэвид Шмитт
148
Принятый ответ хорош, но мне кажется хорошей причиной поместить предложения using вне пространства имен. Если я нахожусь в пространстве имен Outer.Inner, я ожидаю, что он будет использовать класс Math из Outer.Inner, а не System.Math.
Фрэнк Уоллис
7
Я тоже согласен с этим. Принятый ответ верен в том, что он технически описывает разницу. Однако тому или иному классу понадобится явная выноска. Я бы очень хотел, чтобы "Math" разрешало мой собственный локальный класс, а "System.Math" ссылается на внешний класс - даже если System.Math использовался как "Math" до того, как существовал Outer.Math. Да, это больше работы, чтобы исправить сколько-нибудь уже существующих ссылок, но это также может быть подсказкой, что, возможно, Outer.Math должно иметь другое имя!
mbmcavoy
13
Отличный ответ, но мне кажется, что я бы хотел размещать не-фреймворк, используя операторы локально, и оставить фреймворк, использующий операторы, глобальным. У кого-нибудь есть дальнейшее объяснение, почему я должен полностью изменить свои предпочтения? Кроме того, откуда это взялось, что шаблоны в VS2008 используют вне пространства имен?
Тимин
31
Я думаю, что это скорее плохое соглашение об именах, чем изменение места вашего использования. В вашем решении не должно быть класса с именем Math
jDeveloper
455

В этой теме уже есть несколько отличных ответов, но я чувствую, что могу добавить немного больше подробностей с этим дополнительным ответом.

Во-первых, помните, что объявление пространства имен с точками, например:

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

полностью эквивалентно:

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

Если вы хотите, вы можете поместить usingдирективы на всех этих уровнях. (Конечно, мы хотим иметь usings только в одном месте, но это будет законно в зависимости от языка.)

Правило для определения того, какой тип подразумевается, можно условно сформулировать следующим образом: сначала ищите самую внутреннюю «область» для совпадения, если там ничего не найдено, переходите на один уровень к следующей области и ищите там, и так далее , пока совпадение не найдено. Если на каком-то уровне найдено более одного совпадения, если один из типов относится к текущей сборке, выберите его и выдайте предупреждение компилятора. В противном случае сдавайтесь (ошибка времени компиляции).

Теперь давайте подробно рассмотрим, что это значит в конкретном примере с двумя основными соглашениями.

(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 генерировать новый класс в новом файле).

Одним (крошечным) преимуществом использования извне является то, что вы можете использовать директивы using для глобального атрибута, например, [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

Согласно Hanselman - Использование директивы и сборки Загрузка ... и другие подобные статьи, технически нет никакой разницы.

Я предпочитаю размещать их вне пространства имен.

Квинтин Робинсон
источник
3
@Chris M: э-э ... ссылка, опубликованная в ответе, указывает на то, что нет никакой пользы от «против», фактически показывает пример, который фальсифицирует утверждение, сделанное в ссылке, которую вы разместили ...
Джонни
2
Да, я не полностью прочитал тему, но купил, когда MVP сказали, что это правильно. Парень опровергает это, объясняет это и показывает свой код дальше ... "IL, который генерирует компилятор C #, одинаков в любом случае. Фактически, компилятор C # не генерирует точно ничего, соответствующего каждой директиве using. Директивы using являются чисто C # ism, и они не имеют никакого значения для самой .NET. ( Неправильно
Крис Макки,
84
Пожалуйста, включите резюме ссылки. Когда связь нарушается (потому что это будет происходить, достаточно времени), вдруг ответ 32 upvotes только стоит My style is to put them outside the namespaces.- едва ли ответ на все.
Ноябрь
11
Утверждение здесь просто неверно ... есть техническая разница, и ваша собственная цитата говорит об этом ... на самом деле, в этом все дело. Пожалуйста, удалите этот ошибочный ответ ... есть гораздо лучшие и точные.
Джим Балтер
53

Согласно документации StyleCop:

SA1200: использованиеDirectivesMustBePlacedWithinNamespace

Причина. Директива AC # using размещена вне элемента пространства имен.

Описание правила Нарушение этого правила происходит, когда директива using или директива using-alias размещаются вне элемента пространства имен, если файл не содержит никаких элементов пространства имен.

Например, следующий код приведет к двум нарушениям этого правила.

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 выделяется. Если директива using перемещается внутри пространства имен, как показано ниже, произойдет ошибка компилятора:

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. Когда директива using-alias размещается вне элемента пространства имен, компилятор выбирает локальное определение Guid, определенное в локальном пространстве имен, и полностью игнорирует директиву using-alias, определенную вне пространства имен. Это, к сожалению, неочевидно при чтении кода.

Однако, когда директива using-alias размещается в пространстве имен, компилятору приходится выбирать между двумя различными, конфликтующими типами Guid, оба определены в одном и том же пространстве имен. Оба эти типа предоставляют соответствующий конструктор. Компилятор не может принять решение, поэтому он отмечает ошибку компилятора.

Размещение директивы using-alias вне пространства имен является плохой практикой, поскольку это может привести к путанице в ситуациях, подобных этой, когда неясно, какая версия типа фактически используется. Это может привести к ошибке, которую сложно диагностировать.

Размещение директив using-alias в элементе namespace устраняет это как источник ошибок.

  1. Несколько пространств имен

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

Важно отметить, что, когда код был написан с использованием директив, размещенных вне пространства имен, следует соблюдать осторожность при перемещении этих директив в пространстве имен, чтобы гарантировать, что это не изменит семантику кода. Как объяснялось выше, размещение директив using-alias в элементе пространства имен позволяет компилятору выбирать между конфликтующими типами таким образом, чтобы этого не происходило, если директивы размещаются вне пространства имен.

Как исправить нарушения Чтобы исправить нарушение этого правила, переместите все с использованием директив и директив using-alias в элемент пространства имен.

JaredCacurak
источник
1
@Jared - как я отмечал в своем ответе, мой предпочтительный обходной путь / решение состоит в том, чтобы в каждом файле был только один класс. Я думаю, что это довольно распространенное соглашение.
benPearce
24
Действительно, это также правило StyleCop! SA1402: документ AC # может содержать только один класс на корневом уровне, если только все классы не являются частичными и относятся к одному типу. Демонстрируя одно правило, нарушая другое просто капает с неправильным соусом.
Задание
6
Upvoted за то, что был первым ответом, чтобы фактически покрыть это с точки зрения StyleCop. Лично мне нравится визуальное ощущение usings вне пространства имен. Inner usings выглядит так безобразно для меня. :)
nawfal
2
Наконец хороший ответ на вопрос. И комментарий benPearce не имеет значения ... это не имеет никакого отношения к количеству классов в файле.
Джим Балтер
35

Существует проблема с размещением операторов using в пространстве имен, когда вы хотите использовать псевдонимы. Псевдоним не извлекает выгоду из предыдущих 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(но не внутри класса / интерфейса / и т. Д.).
Джеппе Стиг Нильсен
@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

Одна морщина, с которой я столкнулся (это не покрыто другими ответами):

Предположим, у вас есть эти пространства имен:

  • Something.Other
  • Parent.Something.Other

При использовании 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"
    }
  }
}

Вы можете создать этот файл конфигурации на уровне решения и добавить его в свои проекты как «Существующий файл ссылок», чтобы разделить конфигурацию также со всеми вашими проектами.

SOTN
источник
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, которые должны идти впереди.
Авель