К тому моменту, когда вы будете читать эту статью, Build — конференция разработчиков Microsoft — уже закончится, и разработчики будут размышлять о том, как реагировать на все, что было представлено: немедленно принять, наблюдать с легким волнением или пока игнорировать. Для разработчиков, использующих .NET/C#, самым важным, несомненно, было объявление о выпуске следующей версии компилятора C# («Roslyn») как ПО с открытым исходным кодом. С ним связаны и сами языковые усовершенствования. Даже если у вас нет планов немедленного перехода на C# vNext, которую я буду здесь называть неофициально C# 6.0, то, как минимум, вы должны знать о ее средствах и отметить себе то, что может подтолкнуть вас к немедленному переходу на нее.
В этой статье я углублюсь в детали того, что уже доступно в C# 6.0 на момент написания статьи (март 2014 г.) или можно скачать как открытый исходный код с roslyn.codeplex.com. Я буду ссылаться на все это как на единый выпуск, который я назову March Preview. Специфичные для C# средства в этом March Preview полностью реализованы в компиляторе, безо всякой зависимости от обновленной Microsoft .NET Framework или исполняющей среды. То есть вы можете использовать C# 6.0 в своей разработке без обновления .NET Framework. По сути, установка компилятора C# 6.0 из этого выпуска требует лишь установить расширение для Visual Studio 2013, которое в свою очередь обновляет целевые файлы MSBuild.
По мере того, как я буду описывать очередное средство C# 6.0, возможно, вы захотите принять во внимание следующее.
- Был ли приемлемый способ кодирования той же функциональности в прошлом, а не просто некий «синтаксический сахар»? Например, у фильтрации исключений нет эквивалента в C# 5.0, тогда как у основных конструкторов (primary constructors) — есть.
- Доступно ли это средство в March Preview? Большинство описываемых мной средств доступно в этом выпуске, но некоторые (например новый бинарный литерал) отсутствуют.
- Хотите ли вы высказать свои замечания группе в отношении новых языковых средств? Разработка пока находится на относительно раннем этапе жизненного цикла, и группа очень заинтересована услышать ваши соображения по этому выпуску (инструкции по заполнению формы обратной связи см. на msdn.com/Roslyn).
Обдумав такие вопросы, вы сможете лучше оценить значимость новых средств конкретно в ваших разработках.
Индексируемые члены и инициализаторы элементов
Для начала рассмотрим модульный тест на рис. 1.
Рис. 1. Присваивание набора через инициализатор набора (введено в C# 3.0)
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections.Generic;
// ...
[TestMethod]
public void DictionaryIndexWithoutDotDollar()
{
Dictionary<string, string> builtInDataTypes =
new Dictionary<string, string>()
{
{"Byte", "0 to 255"},
// ...
{"Boolean", "True or false."},
{"Object", "An Object."},
{"String", "A string of Unicode characters."},
{"Decimal", "±1.0 × 10e-28 to ±7.9 × 10e28"}
};
Assert.AreEqual("True or false.", builtInDataTypes["Boolean"]);
}
Хотя синтаксис не слишком внятен, код на рис. 1 — это не что иное, как набор «имя-значение». Как таковой, синтаксис мог бы быть гораздо яснее: <index> = <value>. В C# 6.0 это стало возможным благодаря инициализаторам C#-объектов и новому синтаксису индексации членов. Ниже показаны инициализаторы элементов на основе int:
var cppHelloWorldProgram = new Dictionary<int, string>
{
[10] = "main() {",
[20] = " printf(\"hello, world\")",
[30] = "}"
};
Assert.AreEqual(3, cppHelloWorldProgram.Count);
Заметьте: хотя в этом коде использует целое значение для индекса, Dictionary<TKey,TValue> поддерживает любой тип в качестве индекса (если тот поддерживает IComparable<T>). В следующем примере представлена строка для типа данных index и используется инициализатор индексируемого члена для задания значений элементов:
Dictionary<string, string> builtInDataTypes =
new Dictionary<string, string> {
["Byte"] = "0 to 255",
// ...
// Ошибка: смешивание инициализаторов объектов
// и наборов не допускается.
// {" Boolean", "True or false."},
["Object"] = "An Object.",
["String"] = "A string of Unicode characters.",
["Decimal"] = "±1.0 × 10e?28 to ±7.9 × 10e28"
};
Дополняет инициализацию индексируемого члена новый оператор $. Этот строковый синтаксис индексируемого члена введен специально из-за распространенности индексации на основе строк. С помощью этого нового синтаксиса, показанного на рис. 2, можно присваивать значения элементов в стиле, гораздо больше похожим на инициализацию динамических членов (dynamic member invocation) (введенную в C# 4.0), чем на строковую нотацию, использованную в предыдущем примере.
Рис. 2. Инициализация набора присваиванием индексируемого члена как часть инициализатора элемента
[TestMethod]
public void DictionaryIndexWithDotDollar()
{
Dictionary<string, string> builtInDataTypes =
new Dictionary<string, string> {
// Использование индексируемых членов
// в инициализаторах элементов
$Byte = "0 to 255",
// ...
$Boolean = "True or false.",
$Object = "An Object.",
$String = "A string of Unicode characters.",
$Decimal = "±1.0 × 10e?28 to ±7.9 × 10e28"
};
Assert.AreEqual("True or false.", builtInDataTypes.$Boolean);
}
Чтобы понять оператор $, взгляните на вызов функции AreEqual. Обратите внимание на инициализацию члена Dictionary «$Boolean» в переменной builtInDataTypes, хотя в Dictionary нет члена «Boolean». Такой явный член не требуется, потому что оператор $ инициализирует индексируемый член в словаре, а это эквивалентно вызову buildInDataTypes["Boolean"].
Как и в случае любой операции на основе строк, при компиляции не проверяется, что элемент строкового индекса (например, «Boolean») существует в словаре. В итоге за оператором $ можно указывать любое допустимое в C# имя члена (чувствительное к регистру букв).
Чтобы полностью оценить синтаксис индексируемых членов, примите во внимание доминирование строковых индексаторов в таких форматах данных со слабой типизацией, как XML, JSON, CSV, и даже при операциях поиска в базах данных (предполагая, что никакой генерации кода со стороны Entity Framework не используется). Так, рис. 3 демонстрирует строковый индексируемый член с помощью инфраструктуры Newtonsoft.Json.
Рис. 3. Использование индексируемого метода с JSON-данными
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Newtonsoft.Json.Linq;
// ...
[TestMethod]
public void JsonWithDollarOperatorStringIndexers()
{
// Дополнительные типы данных опущены для большей ясности
string jsonText = @"
{
'Byte': {
'Keyword': 'byte',
'DotNetClassName': 'Byte',
'Description': 'Unsigned integer',
'Width': '8',
'Range': '0 to 255'
},
'Boolean': {
'Keyword': 'bool',
'DotNetClassName': 'Boolean',
'Description': 'Logical Boolean type',
'Width': '8',
'Range': 'True or false.'
},
}";
JObject jObject = JObject.Parse(jsonText);
Assert.AreEqual("bool", jObject.$Boolean.$Keyword);
}
И последнее в этой связи (на случай, если это еще не стало очевидным). Синтаксис оператора $ работает только с индексами, имеющими тип string (например, Dictionary<string, …>).
Автоматически реализуемые свойства с инициализаторами
Сегодня инициализация класса иногда бывает весьма утомительной. Возьмем, к примеру, тривиальный случай с пользовательским типом-набором (скажем, Queue<T>), который на внутреннем уровне поддерживает закрытое свойство System.Collections.Generic.List<T>, в котором хранит список элементов. При создании экземпляра этого набора вы должны инициализировать очередь списком содержащихся в нем элементов. Однако разумные варианты выполнения этой операции со свойством требуют наличия вспомогательного поля наряду с инициализатором или конструктора, комбинация которых практически удваивает количество необходимого кода.
В C# 6.0 имеется сокращенный синтаксис: инициализаторы автоматически реализуемых свойство (auto-property initializers). Теперь вы можете присваивать значения таким свойствам напрямую:
class Queue<T>
{
private List<T> InternalCollection { get; } =
new List<T>;
// Реализация очереди
// ...
}
Заметьте, что в этом случае свойство предназначено только для чтения (не определен аксессор set). Но этому свойству можно присвоить значение в момент объявления. Также поддерживается свойство для чтения и записи с аксессором set.
Основные конструкторы
По аналогии с инициализаторами свойств C# 6.0 предоставляет синтаксические сокращения для определения конструктора. Рассмотрим наиболее распространенный в C# конструктор с проверкой свойства (рис. 4).
Рис. 4. Распространенный шаблон конструктора
[Serializable]
public class Patent
{
public Patent(string title , string yearOfPublication)
{
Title = title;
YearOfPublication = yearOfPublication;
}
public Patent(string title, string yearOfPublication,
IEnumerable<string> inventors)
: this(title, yearOfPublication)
{
Inventors = new List<string>();
Inventors.AddRange(inventors);
}
[NonSerialized] // для примера
private string _Title;
public string Title
{
get
{
return _Title;
}
set
{
if (value == null)
{
throw new ArgumentNullException("Title");
}
_Title = value;
}
}
public string YearOfPublication { get; set; }
public List<string> Inventors { get; private set; }
public string GetFullName()
{
return string.Format("{0} ({1})", Title, YearOfPublication);
}
}
В этом шаблоне конструктора стоит отметить несколько моментов.
- Тот факт, что свойство требует проверки, приводит к объявлению нижележащего поля свойства.
- Синтаксис конструктора слегка перегружен слишком часто повторяющимися «public class Patent{ public Patent(...».
- В довольно тривиальном сценарии Title в разных регистрах букв встречается аж семь раз, не считая проверки.
- Инициализация свойства требует явной ссылки на это свойство из конструктора.
Чтобы убрать часть пустых формальностей из этого шаблона без утраты специфики языка, в C# 6.0 введены инициализаторы свойств и основные конструкторы (primary constructors), как показано на рис. 5.
Рис. 5. Применение основного конструктора
[Serializable]
public class Patent(string title, string yearOfPublication)
{
public Patent(string title, string yearOfPublication,
IEnumerable<string> inventors)
:this(title, yearOfPublication)
{
Inventors.AddRange(inventors);
}
private string _Title = title;
public string Title
{
get
{
return _Title;
}
set
{
if (value == null)
{
throw new ArgumentNullException("Title");
}
_Title = value;
}
}
public string YearOfPublication { get; set; } = yearOfPublication;
public List<string> Inventors { get; } = new List<string>();
public string GetFullName()
{
return string.Format("{0} ({1})", Title, YearOfPublication);
}
}
В комбинации с инициализаторами свойств синтаксис основных конструкторов упрощает синтаксис C#-конструкторов.
- Автоматически реализуемые свойства, будь они только для чтения (см. свойство Inventors с единственным аксессором get) или для чтения и записи (см. свойство YearOfPublication с аксессорами set и get), поддерживают инициализацию, чтобы начальное значение можно было задать свойству при объявлении этого свойства. Синтаксис совпадает с тем, который применяется при присваивании полям значений по умолчанию в момент объявления (например, _Title, присвоенное при объявлении).
- По умолчанию параметры основного конструктора недоступны извне инициализатора. Так, в этом классе не объявлено поле yearOfPublication.
- При использовании инициализаторов в свойствах только для чтения (лишь аксессор get) обеспечить проверку нельзя. (Дело в том, что в нижележащей реализации IL параметр основного конструктора присваивается вспомогательному полю. Также стоит отметить тот факт, что вспомогательное поле будет определяться в IL как доступное только для чтения, если у автоматически реализуемого свойства имеется только аксессор get.)
- Основной конструктор, если он указан, всегда будет (и должен) выполняться последним в цепочке конструкторов, а значит, у него не может быть инициализатора this(...).
В качестве другого примера возьмем объявление struct, которая должна быть неизменяемой. Ниже показана реализация на основе свойства (в противоположность атипичного подхода с использованием открытого поля):
struct Pair(string first, string second, string name)
{
public Pair(string first, string second) :
this(first, second, first+"-"+second)
{
}
public string First { get; } = second;
public string Second { get; } = first;
public string Name { get; } = name;
// ...
}
Заметьте, что в реализации Pair есть второй конструктор, который вызывает основной конструктор. В принципе, все конструкторы struct должны прямо или косвенно вызывать основной конструктор через вызов инициализатора this(...). Иначе говоря, не обязательно, чтобы все конструкторы напрямую вызывали основной конструктор, но в конце цепочки конструкторов вызов основного конструктора должен происходить. Это нужно потому, что именно основной конструктор (primary constructor) вызывает инициализатор базового конструктора (base constructor initializer) и что это обеспечивает некоторую защиту от некоторых распространенных ошибок инициализации. (Обратите внимание: как и в C# 1.0 по-прежнему можно создать экземпляр struct без вызова конструктора. Например, именно так и происходит, когда создается экземпляр массива конструкций struct.)
Является основной конструктор пользовательской struct или классом, вызов базового конструктора осуществляется либо неявно (вызовом конструктора по умолчанию базового класса), либо явно (вызовом конкретного конструктора базового класса). В последнем случае, чтобы пользовательское исключение вызывало конкретный конструктор System.Exception, целевой конструктор указывается за основным конструктором:
class UsbConnectionException :
Exception(string message, Exception innerException,
HidDeviceInfo hidDeviceInfo) : base(message, innerException)
{
public HidDeviceInfo HidDeviceInfo { get; } = hidDeviceInfo;
}
Вы должны понимать одну деталь, чтобы предотвратить дублирование (потенциально несовместимое) основных конструкторов в частичных классах. При наличии нескольких частей частичного класса только в одном объявлении этого класса можно определять основной конструктор. Аналогично только этот основной конструктор может указывать вызов базового конструктора.
Есть один серьезный подвох в отношении основных конструкторов в том виде, в каком они реализованы в March Preview: нет никакого способа обеспечить проверку для любого из параметров основного конструктора. А поскольку инициализаторы свойств допустимы только для автоматически реализуемых свойств, нет и способа реализовать проверку в реализации свойства, что потенциально позволяет присвоить через аксессоры set открытого свойства недопустимые данные. Очевидный обходной путь на данный момент — вообще не использовать основной конструктор, когда важна проверка.
Сейчас рассматривается релевантная функциональность под названием «параметр-поле» (field parameter). Включение в параметр основного конструктора модификатора доступа (например, private string title) приведет к тому, что данный параметр будет захвачен в область видимости класса как поле с именем title — это поле получает то же имя с тем же регистром букв, что и параметр. Как таковой, title доступен из свойства Title или любого другого члена экземпляра класса. Более того, модификатор доступа позволяет использовать весь синтаксис поля, включая дополнительные модификаторы вроде readonly или даже атрибуты наподобие следующих:
public class Person(
[field: NonSerialized] private string firstName, string lastName)
Заметьте, что без модификатора доступа другие модификаторы (в том числе, атрибуты) запрещены.
(На момент написания статьи у меня не было реализации параметра-поля, но группа разработки этого языка уверила меня, что все необходимое будет включено в версию Microsoft Build, так что вы сможете опробовать параметры-поля к моменту чтения данной статьи. Однако, учитывая сравнительную «незрелость» этой функциональности, без колебаний оставляйте свои замечания на msnd.com/Roslyn, чтобы они были учтены до того, как весь процесс зайдет слишком далеко для каких-либо изменений.)
Выражения, использующие static
Еще один «синтаксический сахар» C# 6.0 — ключевое слово static. С его помощью можно исключить явную ссылку на тип при вызове статического метода. Более того, использование static позволяет вам вводить только методы расширения для конкретного класса, а не все методы расширения в каком-то пространстве имен. На рис. 6 дан пример «Hello World» с использованием static в System.Console.
Рис. 6. Упрощение громоздкого кода с помощью static
using System;
using System.Console;
public class Program
{
private static void Main()
{
ConsoleColor textColor = ForegroundColor;
try
{
ForegroundColor = ConsoleColor.Red;
WriteLine("Hello, my name is Inigo Montoya... Who are you?: ");
ForegroundColor = ConsoleColor.Green;
string name = ReadLine();
ForegroundColor = ConsoleColor.Red;
WriteLine("I must know.");
ForegroundColor = ConsoleColor.Green;
WriteLine("Get used to disappointment");
}
finally
{
ForegroundColor = textColor;
}
}
}
В этом примере квалификатор Console был отброшен девять раз. Согласен, пример надуманный, но даже в таком случае суть понятна. Зачастую префикс типа в статическом члене (включая свойства) не добавляет значимой ценности, и его исключение дает код, более простой в чтении и написании.
Обсуждается и вторая область применения static, не работающая в March Preview. Суть — в поддержке импорта только методов расширения конкретного типа. Возьмите, к примеру, пространство имен utility, которое включает многочисленные статические типы с методами расширения. Без использования static, импортируются все методы расширения в этом пространстве имен. Однако с помощью static можно закрепить доступные методы расширения за конкретным типом, а не за пространством имен. В итоге вы могли бы вызывать стандартный оператор LINQ-запроса, просто указывая «using System.Linq.Enumerable», а не все пространство имен System.Linq.
К сожалению, это преимущество доступно не всегда (по крайней мере, в March Preview это так), поскольку static поддерживается только статическими типами; именно по этой причине на рис. 6 нет выражения «using System.ConsoleColor». Останется ли это ограничение — это пока вопрос. А что вы думаете на этот счет?
Выражения объявления
Далеко не редкость, когда, находясь в процессе написания какой-то конструкции, вы обнаруживаете, что вам нужно объявить некую переменную специально для этой конструкции. Рассмотрим два примера:
- при кодировании int.TryParse вы осознаете, что вам требуется переменная, объявленная для выходного аргумента (out argument), в которой будут храниться результаты разбора;
- при написании конструкции for вы сталкиваетесь с необходимостью кешировать некий набор (скажем, результата LINQ-запроса), чтобы избежать многократного повторения запроса.
Чтобы устранить эти и другие раздражающие моменты, в C# 6.0 введены выражения объявления. Это означает, что вы не обязаны ограничивать объявления переменных только конструкциями, а можете использовать их и внутри выражений. На рис. 7 приведены два примера.
Рис. 7. Примеры выражений объявления
public string FormatMessage(string attributeName)
{
string result;
if(! Enum.TryParse<FileAttributes>(attributeName,
out var attributeValue) )
{
result = string.Format(
"'{0}' is not one of the possible {2} option combinations ({1})",
attributeName, string.Join(",", string[] fileAtrributeNames =
Enum.GetNames(typeof (FileAttributes))),
fileAtrributeNames.Length);
}
else
{
result = string.Format("'{0}' has a corresponding value of {1}",
attributeName, attributeValue);
}
return result;
}
В первом выделении на рис. 7 переменная attributeValue объявляется в строке с вызовом Enum.TryParse, а не заранее в отдельном объявлении. Аналогично в вызове string.Join «на лету» появляется объявление fileAttributeNames. Это позволяет обращаться потом к Length в той же конструкции. (Заметьте, что fileAttributeNames.Length — это подставной параметр {2} в вызове string.Format, хотя ранее он появлялся в форматирующей строке; тем самым появляется возможность объявить fileAttributeNames до обращения к ней.)
Область видимости выражения объявления свободно определяется как область видимости конструкции, в которой появляется это выражение. На рис. 7 область видимости attributeValue соответствует таковой для конструкции if-else, что делает ее доступной в обоих блоках условия (true и false). Аналогично fileAttributeNames доступна только в первой половине конструкции if — той части, которая соответствует области видимости вызова выражения string.Format.
Зачастую префикс типа в статическом члене не добавляет значимой ценности, и его исключение дает код, более простой в чтении и написании.
По возможности компилятор будет разрешать использование в объявлении неявно типизируемых переменных (var), логически распознавая тип данных по инициализатору (присваиванию в объявлении). Однако в случае out-аргументов для поддержки неявно типизируемых переменных может применяться сигнатура целевого вызова, даже если инициализатора нет. Тем не менее, логическое распознавание возможно не всегда и, более того, может оказаться не лучшим выбором с точки зрения читаемости кода. Например, в случае TryParse на рис. 7 var работает только потому, что указан аргумент-тип (FileAttributes). Без этого var-объявление не прошло бы компиляцию и пришлось бы задействовать явный тип данных:
Enum.TryParse(attributeName, out FileAttributes attributeValue)
Во втором примере выражения объявления на рис. 7 явное объявление string[] идентифицирует тип данных как массив (а не List<string>, например). Универсальное правило таково применения var таково: старайтесь избегать неявно типизируемых переменных, когда конечный тип данных не очевиден.
Все примеры выражений объявления на рис. 7 можно было бы закодировать в виде простых объявлений переменных до присваивания им каких-либо значений.
Улучшения в обработке исключений
В C# 6.0 появились два новых средства, связанных с обработкой исключений. Первое — усовершенствование синтаксиса async и await, а второе — поддержка фильтрации исключений.
Когда в C# 5.0 ввели ключевые слова async и await, разработчики получили сравнительно простой способ кодировать по асинхронному шаблону на основе задач (Task-based Asynchronous Pattern, TAP), когда компилятор берет на себя тяжелую и монотонную работу по преобразованию C#-кода в серию продолжений задач (task continuations). К сожалению, в том выпуске группе не удалось включить поддержку для использования await из блоков catch и finally. Как оказалось, потребность в такого рода вызовах даже больше, чем ожидалось изначально. Поэтому кодировщикам на C# 5.0 приходилось применять довольно замысловатые обходные варианты (например, использовать шаблон awaiter). В C# 6.0 с этим покончено, и теперь вы можете делать await-вызовы из блоков catch и finally (такие вызовы уже поддерживались в блоках try), как показано на рис. 8.
Рис. 8. Await-вызовы из блока catch
try
{
WebRequest webRequest =
WebRequest.Create("http://IntelliTect.com");
WebResponse response =
await webRequest.GetResponseAsync();
// ...
}
catch (WebException exception)
{
await WriteErrorToLog(exception);
}
Другое усовершенствование в обработке исключений в C# 6.0 — поддержка фильтров исключений — выводит данный язык на тот же современный уровень, что и у других .NET-языков, а именно: Visual Basic .NET и F#. Эта функциональность демонстрируется на рис. 9.
Рис. 9. Использование фильтров исключений для захвата только определенного исключения
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.ComponentModel;
using System.Runtime.InteropServices;
// ...
[TestMethod][ExpectedException(typeof(Win32Exception))]
public void ExceptionFilter_DontCatchAsNativeErrorCodeIsNot42()
{
try
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
catch (Win32Exception exception)
if (exception.NativeErrorCode == 0x00042)
{
// Дано лишь для ясности (не требуется)
Assert.Fail("No catch expected.");
}
}
Обратите внимание на дополнительное выражение if, которое следует за выражением catch. Блок catch теперь проверяет не только тот факт, что тип исключения — Win32Exception (или производный от него), но и дополнительные условия: в этом примере конкретное значение кода ошибки. В модульном тесте на рис. 9 ожидается, что блок catch не захватит исключение (хотя тип исключения подходит); вместо этого исключение будет пропущено и обработано атрибутом ExpectedException в методе теста.
Заметьте: в отличие от некоторых других средств C# 6.0, рассмотренных ранее (в частности, основного конструктора), до появления C# 6.0 не было никакого эквивалентного альтернативного способа кодирования фильтров исключений. До настоящего момента единственный подход заключался в захвате всех исключений конкретного типа, явной проверке контекста исключения и последующей повторной генерации исключения, если текущее состояние не соответствовало допустимому сценарию захвата исключения. Другими словами, фильтрация исключений в C# 6.0 обеспечивает функциональность, которой прежде никогда не было в C#.
Дополнительные форматы числовых литералов
Хотя в March Preview это еще не реализовано, в C# 6.0 будет введен разделитель разрядов (digit separator) — знак подчеркивания (_) как средство разделения разрядов в числовом литерале (десятичном, шестнадцатеричном или двоичном). Разряды можно разбивать на любые подходящие в вашем сценарии группы. Например, максимальное значение целого типа можно было бы разбить на группы по тысячам:
int number = 2_147_483_647;
Это облегчает восприятие порядка числового значения, будь то десятичное, шестнадцатеричное или двоичное.
Разделитель разрядов скорее всего будет особенно полезен для нового числового двоичного литерала в C# 6.0. Хотя это не требуется в каждой программе, наличие двоичного литерала могло бы упростить сопровождение кода при работе с бинарной логикой или с перечислениями на основе флагов (flag-based enums). Возьмем, к примеру, перечисление FileAttribute, показанное на рис. 10.
Рис. 10. Присваивание двоичных литералов значениям перечисления
[Serializable][Flags]
[System.Runtime.InteropServices.ComVisible(true)]
public enum FileAttributes
{
ReadOnly = 0b00_00_00_00_00_00_01, // 0x0001
Hidden = 0b00_00_00_00_00_00_10, // 0x0002
System = 0b00_00_00_00_00_01_00, // 0x0004
Directory = 0b00_00_00_00_00_10_00, // 0x0010
Archive = 0b00_00_00_00_01_00_00, // 0x0020
Device = 0b00_00_00_00_10_00_00, // 0x0040
Normal = 0b00_00_00_01_00_00_00, // 0x0080
Temporary = 0b00_00_00_10_00_00_00, // 0x0100
SparseFile = 0b00_00_01_00_00_00_00, // 0x0200
ReparsePoint = 0b00_00_10_00_00_00_00, // 0x0400
Compressed = 0b00_01_00_00_00_00_00, // 0x0800
Offline = 0b00_10_00_00_00_00_00, // 0x1000
NotContentIndexed = 0b01_00_00_00_00_00_00, // 0x2000
Encrypted = 0b10_00_00_00_00_00_00 // 0x4000
}
Теперь благодаря двоичным числовым литералам вы можете более четко показывать, какие флаги устанавливаются, а какие — нет. Это заменяет шестнадцатеричную нотацию, приведенную в комментариях, или подход с вычислением сдвига при компиляции:
Encrypted = 1<<14.
(Желающие немедленно опробовать эту функциональность могут сделать это в Visual Basic .NET в сочетании с выпуском March Preview.)
Заключение
Учитывая, что в язык внесены только эти изменения, вы поймете, что в C# 6.0 нет ничего особо революционного. Если сравнить эти изменения с теми, которые были в других основных версиях, например обобщения в C# 2.0, LINQ в C# 3.0 или TAP в C# 5.0, то C# 6.0 в большей мере является «промежуточной», а не основной версией. (Самое потрясающее новшество в том, что компилятор будет выпущен как ПО с открытым исходным кодом.) Но, хотя эта версия не вносит революционные изменения в кодирование на C#, это не значит, что в ней нет реального прогресса, благодаря которому исключаются некоторые раздражающие моменты в кодировании и неэффективности; при повседневном использовании вы быстро сочтете эти изменения как сами собой разумеющимися.