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

1827

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

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

  • 3
    Иногда это имеет значение, где вы используете: stackoverflow.com/questions/292535/linq-to-sql-designer-bug
  • 78
    Просто для справки, есть некоторые последствия помимо вопроса о нескольких классах на файл, поэтому, если вы новичок в этом вопросе, пожалуйста, продолжайте читать.
Показать ещё 3 комментария
Теги:
namespaces
code-organization
stylecop

10 ответов

1824
Лучший ответ

На самом деле есть (тонкая) разница между ними. Представьте, что у вас есть следующий код в 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.

  • 25
    Это гораздо лучшая причина использовать операторы локально, чем аргумент Марка, состоящий из нескольких пространств имен в одном файле. Особенно, если компиляция может и будет жаловаться на конфликт имен (см. Документацию StyleCop для этого правила (например, опубликованную Jared)).
  • 0
    Не забудьте сделать это вместо того, чтобы просто перейти к глобальному :: квалификатору.
Показать ещё 17 комментариев
392

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

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

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

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

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

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

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

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

(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, поиск идет в следующем порядке:

  • Вложенные типы внутри C (включая унаследованные вложенные типы)
  • Типы в текущем пространстве имен MyCorp.TheProduct.SomeModule.Utilities
  • Типы в пространстве имен MyCorp.TheProduct.SomeModule
  • Типы в MyCorp.TheProduct
  • Типы в MyCorp
  • Типы в пустом пространстве имен (глобальное пространство имен)
  • Типы в 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 выполняется в следующем порядке:

  • Вложенные типы внутри C (включая унаследованные вложенные типы)
  • Типы в текущем пространстве имен MyCorp.TheProduct.SomeModule.Utilities
  • Типы в System, System.Collections.Generic, System.Linq, MyCorp.TheProduct, MyCorp.TheProduct.OtherModule, MyCorp.TheProduct.OtherModule.Integration и ThirdParty
  • Типы в пространстве имен MyCorp.TheProduct.SomeModule
  • Типы в MyCorp
  • Типы в пустом пространстве имен (глобальное пространство имен)

(Обратите внимание, что MyCorp.TheProduct является частью "3." и поэтому не требуется между "4." и "5.".)

Заключительные замечания

Независимо от того, помещаете ли вы сообщения внутри или вне объявления пространства имен, всегда существует вероятность того, что кто-то позже добавит новый тип с идентичным именем в одно из пространств имен с более высоким приоритетом.

Кроме того, если вложенное пространство имен имеет то же имя, что и тип, это может вызвать проблемы.

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

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

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

  • 26
    Это лучшее объяснение, потому что оно подчеркивает тот факт, что позиция выражений «использование» является осознанным решением разработчика. Ни в коем случае не следует безрассудно менять местоположение выражений «использования», не понимая их последствий. Поэтому правило StyleCop просто тупое.
178

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

using ThisNamespace.IsImported.InAllNamespaces.Here;

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

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

namespace Namespace3
{ 
   using ThisNamespace.IsImported.InJustNamespace3;
}
  • 0
    пространства имен обеспечивают логическое разделение, а не физическое (файловое).
  • 9
    Это не совсем верно, что нет никакой разницы; using директив внутри блоков namespace может ссылаться на относительные пространства имен, основанные на включающем блоке namespace .
Показать ещё 1 комментарий
56

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

Мое предпочтение заключается в том, чтобы помещать их вне пространств имен.

  • 2
    @Chris M: э-э ... ссылка, опубликованная в ответе, указывает на то, что нет никакой пользы от «против», фактически показывает пример, который фальсифицирует утверждение, сделанное в ссылке, которую вы разместили ...
  • 2
    Да, я не полностью прочитал тему, но купил, когда MVP сказали, что это правильно. Парень опровергает это, объясняет это и показывает свой код дальше ... "IL, который генерирует компилятор C #, одинаков в любом случае. Фактически, компилятор C # не генерирует точно ничего, соответствующего каждой директиве using. Директивы using являются C # ism, и они не имеют никакого значения для самой .NET. (Не верно для использования операторов, но это совсем другое.) Groups.google.com/group/wpf-disciples/msg/781738deb0a15c46
Показать ещё 2 комментария
46

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

SA1200: UsingDirectivesMustBePlacedWithinNamespace

Причина Директива директивы С# помещается вне элемента пространства имен.

Описание правила Нарушение этого правила происходит, когда директива 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 за пределами пространства имен является плохой практикой, потому что это может привести к путанице в таких ситуациях, когда это не очевидно, какая версия типа фактически используется. Это может потенциально привести к ошибке, которая может быть трудно диагностировать.

Размещение директив с использованием псевдонимов в элементе пространства имен исключает это как источник ошибок.

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

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

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

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

  • 0
    @Jared - как я отмечал в своем ответе, мой предпочтительный обходной путь / решение заключается в том, чтобы в каждом файле был только один класс. Я думаю, что это довольно распространенное соглашение.
  • 23
    Действительно, это также правило StyleCop! SA1402: документ AC # может содержать только один класс на корневом уровне, если только все классы не являются частичными и относятся к одному типу. Демонстрируя одно правило, нарушая другое просто капает с неправильным соусом.
Показать ещё 2 комментария
32

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

Как сказал Jeppe Stig Nielsen , этот поток уже имеет отличные ответы, но я думал, что это довольно очевидная тонкость тоже стоит упомянуть.

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;
    }
}
  • 5
    Это правда, что вы «можете опустить внешнее пространство имен», но это не значит, что вы должны это делать. Для меня это еще один аргумент относительно того, почему использование директив (кроме псевдонимов, как в ответе @ Neo) должно выходить за пределы пространства имен, чтобы принудительно вводить полностью определенные имена пространств имен.
1

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

Когда у вас есть импорт внутри пространства имен, он найдет класс. Если импорт находится за пределами пространства имен, то импорт будет проигнорирован, а класс и пространство имен должны быть полностью определены.

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

Технические причины обсуждаются в ответах, и я думаю, что в конечном итоге речь идет о личных предпочтениях, поскольку разница не так уж велика, и для них обоих есть компромиссы. Шаблон 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"
    }
  }
}

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

-7

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

  • 6
    Нет, на самом деле это плохая идея. Вы не должны основывать расположение между локально и глобально ограниченным использованием директив на том факте, что они были добавлены или нет. Вместо этого рекомендуется располагать их по алфавиту, за исключением ссылок на BCL, которые должны идти впереди.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню