Я запускал StyleCop над некоторым кодом C#, и он продолжает сообщать, что мои директивы using должны находиться внутри пространства имен.
Есть ли техническая причина для размещения директив using внутри, а не за пределами пространства имен?
Иногда имеет значение, где вы указываете использование: stackoverflow.com/questions/292535/linq-to-sql-designer-bug
@ user-12506 - это не очень хорошо работает в средней и большой команде разработчиков, где требуется некоторый уровень согласованности кода. И, как отмечалось ранее, если вы не понимаете различных макетов, вы можете обнаружить крайние случаи, которые работают не так, как вы ожидаете.
Терминология: Это не usingзаявления; это usingдирективы. С другой стороны, оператор using - это языковая структура, которая встречается вместе с другими операторами внутри тела метода и т. д. Например, using (var e = s.GetEnumerator()) { /* ... */ } - это оператор, который примерно такой же, как var e = s.GetEnumerator(); try { /* ... */ } finally { if (e != null) { e.Dispose(); } }.
Если об этом уже никто не упоминал, на самом деле Microsoft тоже рекомендует помещать операторы using внутри объявлений namespace, в их рекомендации по внутреннему кодированию.





Помещение его внутри пространств имен делает объявления локальными для этого пространства имен для файла (в случае, если у вас есть несколько пространств имен в файле), но если у вас есть только одно пространство имен для каждого файла, тогда не имеет большого значения, выходят ли они за пределы или внутри пространства имен.
using ThisNamespace.IsImported.InAllNamespaces.Here;
namespace Namespace1
{
using ThisNamespace.IsImported.InNamespace1.AndNamespace2;
namespace Namespace2
{
using ThisNamespace.IsImported.InJustNamespace2;
}
}
namespace Namespace3
{
using ThisNamespace.IsImported.InJustNamespace3;
}
пространства имен обеспечивают логическое разделение, а не физическое (файловое).
Не совсем верно, что нет разницы; Директивы using в блоках namespace могут ссылаться на относительные пространства имен на основе включающего блока namespace.
Да, знаю. мы установили это в принятом ответе на этот вопрос пять лет назад.
Согласно Hanselman - Использование директивы и сборки Загрузка ... и другим подобным статьям, технически разницы нет.
Я предпочитаю размещать их вне пространств имен.
@Chris M: эээ ... ссылка, опубликованная в ответе, указывает на преимущество нет по сравнению с in vs. out, фактически показывая пример, который фальсифицирует утверждение, сделанное в ссылке, которую вы разместили ...
Да, я не полностью прочитал ветку, но согласился, когда MVP сказали, что это правильно. Парень опровергает это, объясняет это и показывает свой код ниже ... "IL, который генерирует компилятор C#, одинаков в любом случае. Фактически компилятор C# не генерирует ничего, соответствующего каждой директиве using. Директивы using - это чисто C# ism, и они не имеют никакого значения для самой .NET. (Не верно для операторов using, но это нечто совсем другое.) "groups.google.com/group/wpf-disciples/msg/781738deb0a15c46
Пожалуйста, включите краткое содержание ссылки. Когда ссылка не работает (потому что это происходит воля при наличии достаточного времени), внезапно ответ с 32 положительными голосами стоит только My style is to put them outside the namespaces. - едва ли вообще ответ.
Заявление здесь просто неверно ... есть техническая разница, и ваша собственная цитата говорит об этом ... на самом деле, в этом все дело. Пожалуйста, удалите этот ошибочный ответ ... есть гораздо лучшие и точные.
На самом деле между ними есть (тонкая) разница. Представьте, что у вас есть следующий код в 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.
Согласно документации 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.Sample содержит определение, конфликтующее с псевдонимом Guid
Код создает псевдоним типа System.Guid с именем Guid, а также создает собственный тип с именем Guid с соответствующим интерфейсом конструктора. Позже код создает экземпляр типа Guid. Чтобы создать этот экземпляр, компилятор должен выбрать между двумя разными определениями Guid. Когда директива using-alias размещается за пределами элемента пространства имен, компилятор выбирает локальное определение Guid, определенное в локальном пространстве имен, и полностью игнорирует директиву using-alias, определенную вне пространства имен. Это, к сожалению, не очевидно при чтении кода.
Однако, когда директива using-alias расположена в пространстве имен, компилятор должен выбирать между двумя разными конфликтующими типами Guid, которые определены в одном пространстве имен. Оба эти типа предоставляют соответствующий конструктор. Компилятор не может принять решение, поэтому он отмечает ошибку компилятора.
Размещение директивы using-alias вне пространства имен - плохая практика, поскольку это может привести к путанице в таких ситуациях, как эта, где не очевидно, какая версия типа фактически используется. Это потенциально может привести к ошибке, которую будет сложно диагностировать.
Размещение директив using-alias в элементе пространства имен устраняет это как источник ошибок.
Размещение нескольких элементов пространства имен в одном файле, как правило, является плохой идеей, но если и когда это будет сделано, рекомендуется разместить все директивы using внутри каждого из элементов пространства имен, а не глобально в верхней части файла. Это сильно ограничит пространство имен, а также поможет избежать описанного выше поведения.
Важно отметить, что когда код был написан с использованием директив, размещенных за пределами пространства имен, следует проявлять осторожность при перемещении этих директив в пространстве имен, чтобы гарантировать, что это не изменит семантику кода. Как объяснялось выше, размещение директив using-alias в элементе пространства имен позволяет компилятору выбирать между конфликтующими типами способами, которые не будут происходить, если директивы размещены за пределами пространства имен.
Как исправить нарушения Чтобы исправить нарушение этого правила, переместите все директивы using и директивы using-alias в элемент пространства имен.
@Jared - как я отметил в своем ответе, я предпочитаю обходной путь / решение - всегда иметь только один класс для каждого файла. Я считаю, что это довольно распространенная конвенция.
В самом деле, это тоже правило StyleCop! SA1402: документ C# может содержать только один класс на корневом уровне, если все классы не являются частичными и относятся к одному типу. Продемонстрировать одно правило, нарушив другое, просто капает с неправильным соусом.
Проголосовали за то, чтобы быть первым ответом, который действительно освещает это с точки зрения StyleCop. Лично мне нравится визуальное восприятие using за пределами пространства имен. Внутренние using кажутся мне такими уродливыми. :)
Наконец-то хороший ответ на вопрос. И комментарий benPearce не имеет значения ... это не имеет ничего общего с количеством классов в файле.
Если вы хотите использовать псевдонимы, возникает проблема с размещением операторов 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>>>;
Не очень.
Вашему class необходимо имя (идентификатор). У вас не может быть директивы using внутри класса, как вы указываете. Он должен находиться на уровне пространства имен, например, вне самого внешнего namespace или только внутри самого внутреннего namespace (но не внутри класса / интерфейса / и т. д.).
@JeppeStigNielsen Спасибо. Я ошибочно разместил директивы 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, поиск идет в следующем порядке:
C (включая унаследованные вложенные типы)MyCorp.TheProduct.SomeModule.UtilitiesMyCorp.TheProduct.SomeModuleMyCorp.TheProductMyCorpSystem, 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.UtilitiesSystem, System.Collections.Generic, System.Linq, MyCorp.TheProduct, MyCorp.TheProduct.OtherModule, MyCorp.TheProduct.OtherModule.Integration и ThirdPartyMyCorp.TheProduct.SomeModuleMyCorp(Обратите внимание, что MyCorp.TheProduct был частью «3» и поэтому не нужен между «4» и «5».)
Заключительные замечания
Независимо от того, поместите ли вы использование внутри или вне декларации пространства имен, всегда есть вероятность, что кто-то позже добавит новый тип с таким же именем в одно из пространств имен, имеющих более высокий приоритет.
Кроме того, если вложенное пространство имен имеет то же имя, что и тип, это может вызвать проблемы.
Всегда опасно перемещать использование из одного места в другое, потому что иерархия поиска изменяется, и может быть найден другой тип. Поэтому выберите одно соглашение и придерживайтесь его, чтобы вам никогда не пришлось менять использование.
Шаблоны Visual Studio по умолчанию помещают использование вне пространства имен (например, если вы заставляете VS генерировать новый класс в новом файле).
Одно (крошечное) преимущество использования вне заключается в том, что вы можете затем использовать директивы using для глобального атрибута, например [assembly: ComVisible(false)] вместо [assembly: System.Runtime.InteropServices.ComVisible(false)].
Лучше всего, если те дефолт, использующие, например, «использованная литература», используемые в вашем исходном решении, должны находиться за пределами пространств имен, а те, которые являются "новая добавленная ссылка", являются хорошей практикой, вы должны поместить их внутри пространства имен. Это нужно для того, чтобы различать, какие ссылки добавляются.
Нет, вообще-то это плохая идея. Вы не должны основывать расположение между директивами using с локальной и глобальной областью видимости на том факте, что они добавлены недавно или нет. Вместо этого рекомендуется расположить их в алфавитном порядке, за исключением ссылок BCL, которые должны располагаться сверху.
Как и Jeppe Stig Nielsen сказал, в этой ветке уже есть отличные ответы, но я подумал, что об этой довольно очевидной тонкости тоже стоит упомянуть.
Директивы 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 (кроме псевдонимов, как в ответе @ Neo) должны выходить за пределы пространства имен, чтобы принудительно использовать полностью определенные имена пространств имен.
Технические причины обсуждаются в ответах, и я думаю, что в конечном итоге дело доходит до личных предпочтений, поскольку разница не в том, что большой, и есть компромиссы для них обоих. В шаблоне 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"
}
}
}
Вы можете создать этот файл конфигурации на уровне решения и добавить его в свои проекты как «Существующий файл ссылок», чтобы также использовать конфигурацию для всех ваших проектов.
Еще одна тонкость, которая, как мне кажется, не была охвачена другими ответами, связана с тем, что у вас есть класс и пространство имен с тем же именем.
Когда у вас есть импорт внутри пространства имен, он найдет класс. Если импорт находится за пределами пространства имен, импорт будет проигнорирован, а класс и пространство имен должны быть полностью определены.
//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.
}
}
}
Одна морщина, с которой я столкнулся (которая не рассматривается в других ответах):
Предположим, у вас есть эти пространства имен:
Когда вы используете using Something.Otherвне из namespace Parent, это относится к первому (Something.Other).
Однако, если вы используете его внутри этого объявления пространства имен, он ссылается на второй (Parent.Something.Other)!
Есть простое решение: добавить префикс «global::»: документы
namespace Parent
{
using global::Something.Other;
// etc
}
Хорошая точка зрения! Я столкнулся с проблемой, связанной с работой с RazorGenerator. К сожалению, похоже, что он не понимает 'global ::' и преобразует его в 'using global;' :( Жаль, что я не могу выбрать, нужно ли сгенерировать 'using' внутри или за пределами 'namespace' ...
Ссылаясь на внутренние правила Microsoft, имейте в виду, что они написаны кем-то, кто, вероятно, имеет менее десяти лет опыта программирования. Другими словами, они, вероятно, основаны не более чем на личных предпочтениях. Особенно в таких новинках, как C#.
Как правило, внешние директивы using (например, пространства имен System и Microsoft) следует размещать как вне директивы namespace. Это значения по умолчанию, которые следует применять во всех случаях если не указано иное. Это должно включать любые внутренние библиотеки вашей организации, которые не являются частью текущего проекта, или директивы using, которые ссылаются на другие первичные пространства имен в том же проекте. Любые директивы using, которые ссылаются на другие модули в текущем проекте и пространстве имен, должны быть помещены внутри в директиву namespace. Это выполняет две конкретные функции:
Последняя причина существенна. Это означает, что сложнее ввести неоднозначную справочную проблему, которая может быть вызвана изменением не более значительным, чем код рефакторинга. То есть вы перемещаете метод из одного файла в другой, и внезапно обнаруживается ошибка, которой раньше не было. В просторечии «гейзенбаг» - исторически невероятно трудно выследить.
Еще более общее правило, которому следует следовать. Если вы видите что-то присущее языку, которое кажется бесполезным вариантом, предположите, что это НЕ. На самом деле, чем сложнее понять, почему существует такая возможность, тем важнее ее предположить. Изучите различия специфический между двумя вариантами, а затем долго и хорошо подумайте о последствиях. Обычно вы найдете удивительно проницательное и умное решение неясной проблемы, которое разработчик языка поместил туда специально, чтобы облегчить тебе жизнь. Будьте соответственно благодарны и воспользуйтесь этим.
@jwdonahue: Когда меня пригласили присоединиться к команде разработчиков функций C#, я почти десять лет был профессиональным разработчиком компиляторов и языковым дизайнером, и я был безусловно самым молодым человеком в комнате. Команда разработчиков C# наверняка воспользуется прекрасными идеями, исходящими от стажеров; представление о недостатке опыта в проектировании, оценке, определении и реализации языковых функций в группе разработчиков C# не соответствует действительности.
Точно так же этот ответ начинается с причудливого и совершенно противоположного факту предположения о том, что авторы руководящих принципов по разработке фреймворков просто составляют список своих предпочтений. Нет ничего более далекого от правды. Мы активно обсуждали эти руководящие принципы в зале, полном экспертов и заинтересованных сторон; они были продуктом тщательного рассмотрения потребностей профессиональных разработчиков.
@EricLippert, оглядываясь назад, я не должен был озвучивать эту глупую мысль. Извинения.
Просто для справки, есть последствия, выходящие за рамки вопроса о нескольких классах в файле, поэтому, если вы новичок в этом вопросе, продолжайте читать.