Должны ли директивы using находиться внутри или вне пространства имен? - PullRequest
1890 голосов
/ 24 сентября 2008

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

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

Ответы [ 11 ]

1978 голосов
/ 30 сентября 2008

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

398 голосов
/ 19 апреля 2013

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

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

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. Типы в пространстве имен null (глобальное пространство имен)
  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. Типы в пространстве имен null (глобальное пространство имен)

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

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

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

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

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

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

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

188 голосов
/ 24 сентября 2008

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

using ThisNamespace.IsImported.InAllNamespaces.Here;

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

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

namespace Namespace3
{ 
   using ThisNamespace.IsImported.InJustNamespace3;
}
61 голосов
/ 24 сентября 2008

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

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

47 голосов
/ 14 сентября 2009

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

SA1200: использование директив MustBePlacedWithinNamespace

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

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

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

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

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

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

32 голосов
/ 10 октября 2012

Существует проблема с размещением операторов 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>>>;

Не очень.

3 голосов
/ 17 сентября 2016

Как сказал Джепп Стиг Нильсен , у этой ветки уже есть отличные ответы, но я подумал, что эту довольно очевидную тонкость тоже стоит упомянуть.

Директивы

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;
    }
}
1 голос
/ 05 июня 2019

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

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

  • Something.Other
  • Parent.Something.Other

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

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

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

namespace Parent
{
   using global::Something.Other;
   // etc
}
1 голос
/ 24 августа 2018

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

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

//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 голосов
/ 03 июня 2018

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

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

...