Вопрос: Должны ли «использовать» директивы внутри или вне пространства имен?


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

Существует ли техническая причина для usingдирективы внутри вместо внешнего пространства имен?


1717


источник


Ответы:


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


1812



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

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

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


334



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

using ThisNamespace.IsImported.InAllNamespaces.Here;

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

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

namespace Namespace3
{ 
   using ThisNamespace.IsImported.InJustNamespace3;
}

177



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

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


54



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

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

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

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

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

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

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

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

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


44



There is an issue with placing using statements inside the namespace when you wish to use aliases. The alias doesn't benefit from the earlier using statements and has to be fully qualified.

Consider:

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

    class MyClass
    {
    }
}

versus:

using System;

namespace MyNamespace
{
    using MyAlias = DateTime;

    class MyClass
    {
    }
}

This can be particularly pronounced if you have a long-winded alias such as the following (which is how I found the problem):

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

With using statements inside the namespace, it suddenly becomes:

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

Not pretty.


29



As Jeppe Stig Nielsen said, this thread already has great answers, but I thought this rather obvious subtlety was worth mentioning too.

using directives specified inside namespaces can make for shorter code since they don't need to be fully qualified as when they're specified on the outside.

The following example works because the types Foo and Bar are both in the same global namespace, Outer.

Presume the code file Foo.cs:

namespace Outer.Inner
{
    class Foo { }
}

And Bar.cs:

namespace Outer
{
    using Outer.Inner;

    class Bar
    {
        public Foo foo;
    }
}

That may omit the outer namespace in the using directive, for short:

namespace Outer
{
    using Inner;

    class Bar
    {
        public Foo foo;
    }
}

2



The technical reasons are discussed in the answers and I think that it comes to the personal preferences in the end since the difference is not that big and there are tradeoffs for both of them. Visual Studio's default template for creating .cs files use using directives outside of namespaces e.g.

One can adjust stylecop to check using directives outside of namespaces through adding stylecop.json file in the root of the project file with the following:

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

You can create this config file in solution level and add it to your projects as 'Existing Link File' to share the config across all of your projects too.


0



It is a better practice if those default using i.e. "references" used in your source solution should be outside the namespaces and those that are "new added reference" is a good practice is you should put it inside the namespace. This is to distinguish what references are being added.


-7