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

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

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

Ответ 1

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

Ответ 2

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

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

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)].

Ответ 3

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

using ThisNamespace.IsImported.InAllNamespaces.Here;

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

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

namespace Namespace3
{ 
   using ThisNamespace.IsImported.InJustNamespace3;
}

Ответ 5

Согласно документации 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 в элементе пространства имен.

Ответ 6

Существует проблема с размещением с использованием операторов внутри пространства имен, когда вы хотите использовать псевдонимы. Псевдоним не извлекает выгоду из более ранних операторов 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>>>;

Не очень.

Ответ 7

Как сказал 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;
    }
}

Ответ 8

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

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

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

Ответ 9

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

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

Ответ 10

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

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

  • Something.Other
  • Parent.Something.Other

Когда вы используете using Something.Other вне namespace Parent, оно ссылается на первое (Something.Other).

Однако, если вы используете его внутри этого объявления пространства имен, оно ссылается на второе (Parent.Something.Other)!

Есть простое решение: добавить префикс " global:: ": docs

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

Ответ 11

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