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



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



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

1002   10  

10 ответов:

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

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

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

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. типы 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)].

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

using ThisNamespace.IsImported.InAllNamespaces.Here;

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

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

namespace Namespace3
{ 
   using ThisNamespace.IsImported.InJustNamespace3;
}

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

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

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

SA1200: UsingDirectivesMustBePlacedWithinnamespace

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

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

  1. Несколько Пространств Имен

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

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

как исправить нарушения Чтобы исправить нарушение этого правила, переместите все с помощью директивы и директивы 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.

предположим, что файл кода Фу.cs:

namespace Outer.Inner
{
    class Foo { }
}

и бар.cs:

namespace Outer
{
    using Outer.Inner;

    class Bar
    {
        public Foo foo;
    }
}

это может опустить внешнее пространство имен в

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

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

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

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

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

Comments

    Ничего не найдено.