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

StackOverflow https://stackoverflow.com/questions/125319

Вопрос

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

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

Это было полезно?

Решение

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

Это изменится, если вы поставите 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 директива.

Другие советы

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

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

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 то есть поиск идет в таком порядке:

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

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

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

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

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

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

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

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

using ThisNamespace.IsImported.InAllNamespaces.Here;

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

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

namespace Namespace3
{ 
   using ThisNamespace.IsImported.InJustNamespace3;
}

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

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

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

SA1200:usingdirectivesmustbeplacedwithinname пространство

Потому что Директива C# 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.Пример" содержит определение, конфликтующее с псевдонимом "Guid"

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

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

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

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

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

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

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

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

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

Некрасиво.

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

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;
    }
}

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

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

  • Что-то.Другое
  • Родитель.Что-то.Другое

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

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

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

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

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top