Должны ли директивы "using" находиться внутри или вне пространства имен?
Я бегал StyleCop над некоторым кодом C#, и он продолжает сообщать, что мой using директивы должны быть в пространстве имен.
есть ли техническая причина для размещения using директивы внутри, а не вне пространства имен?
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поиск идет в таком порядке:
- вложенные типы внутри
C(включая унаследованные вложенные типы)- типы в текущем пространстве имен
MyCorp.TheProduct.SomeModule.Utilities- типы в пространстве имен
MyCorp.TheProduct.SomeModule- типы в
MyCorp.TheProduct- типы
MyCorp- типы null пространство имен (глобальное пространство имен)
- типы
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- типы 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 в элементе пространства имен устраняет это как источник ошибок.
- Несколько Пространств Имен
размещение нескольких элементов пространства имен в одном файле, как правило, плохая идея, но если и когда это будет сделано, это хорошая идея, чтобы разместить все директивы 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