Обзор новых функций C# 6

Версия 6 языка C# продолжает развивать язык, чтобы иметь менее стандартный код, улучшенную ясность и большую согласованность. Синтаксис более чистой инициализации, возможность использования await в блоках catch/finally и условного значения NULL? оператор особенно полезен.

Примечание.

Сведения о последней версии языка C# версии 7 см. в статье "Новые возможности в C# 7.0"

В этом документе представлены новые возможности C# 6. Он полностью поддерживается компилятором mono, и разработчики могут начать использовать новые функции на всех целевых платформах Xamarin.

Новые возможности видео C# 6

Использование C# 6

Компилятор C# 6 используется во всех последних версиях Visual Studio для Mac. Те, кто использует компиляторы командной строки, должны подтвердить, что mcs --version возвращает значение 4.0 или более поздней версии. Visual Studio для Mac пользователи могут проверка, если у них установлен Mono 4 (или более поздней версии), указав сведения о Visual Studio для Mac > Visual Studio для Mac > показать сведения.

Меньше стандартных

using static

Перечисления и некоторые классы, такие как System.Math, в основном являются владельцами статических значений и функций. В C# 6 можно импортировать все статические элементы типа с помощью одной using static инструкции. Сравните типичную тригонометрическую функцию в C# 5 и C# 6:

// Classic C#
class MyClass
{
    public static Tuple<double,double> SolarAngleOld(double latitude, double declination, double hourAngle)
    {
        var tmp = Math.Sin (latitude) * Math.Sin (declination) + Math.Cos (latitude) * Math.Cos (declination) * Math.Cos (hourAngle);
        return Tuple.Create (Math.Asin (tmp), Math.Acos (tmp));
    }
}

// C# 6
using static System.Math;

class MyClass
{
    public static Tuple<double, double> SolarAngleNew(double latitude, double declination, double hourAngle)
    {
        var tmp = Asin (latitude) * Sin (declination) + Cos (latitude) * Cos (declination) * Cos (hourAngle);
        return Tuple.Create (Asin (tmp), Acos (tmp));
    }
}

using static не делает общедоступные const поля, например Math.PI и Math.Eнапрямую доступными:

for (var angle = 0.0; angle <= Math.PI * 2.0; angle += Math.PI / 8) ... 
//PI is const, not static, so requires Math.PI

использование статических методов расширения

Объект using static работает немного по-другому с методами расширения. Хотя методы расширения записываются с помощью static, они не имеют смысла без экземпляра, на котором следует работать. Поэтому при using static использовании с типом, определяющим методы расширения, методы расширения становятся доступными в целевом типе (тип метода this ). Например, using static System.Linq.Enumerable можно использовать для расширения API IEnumerable<T> объектов без привлечения всех типов LINQ:

using static System.Linq.Enumerable;
using static System.String;

class Program
{
    static void Main()
    {
        var values = new int[] { 1, 2, 3, 4 };
        var evenValues = values.Where (i => i % 2 == 0);
        System.Console.WriteLine (Join(",", evenValues));
    }
}

В предыдущем примере показано различие в поведении: метод Enumerable.Where расширения связан с массивом, а статический метод String.Join можно вызывать без ссылки на String тип.

Выражения nameof

Иногда необходимо ссылаться на имя, которое вы предоставили переменной или полю. В C# 6 nameof(someVariableOrFieldOrType) возвращается строка "someVariableOrFieldOrType". Например, при вызове ArgumentException вы, скорее всего, хотите указать, какой аргумент недопустим:

throw new ArgumentException ("Problem with " + nameof(myInvalidArgument))

Главным преимуществом выражений nameof является то, что они являются типом проверка и совместимы с рефакторингом на основе инструментов. Тип проверка выражений nameof особенно приветствуется в ситуациях, когда string используется для динамического связывания типов. Например, в iOS string используется для указания типа, используемого для прототипов UITableViewCell объектов в объекте UITableView. nameof может гарантировать, что эта связь не завершается ошибкой из-за неправильной или небрежной рефакторинга:

public override UITableViewCell GetCell (UITableView tableView, NSIndexPath indexPath)
{
    var cell = tableView.DequeueReusableCell (nameof(CellTypeA), indexPath);
    cell.TextLabel.Text = objects [indexPath.Row].ToString ();
    return cell;
}

Несмотря на то что можно передать полное имя nameof, возвращается только последний элемент (после последнего .). Например, можно добавить привязку данных в Xamarin.Forms:

var myReactiveInstance = new ReactiveType ();
var myLabelOld.BindingContext = myReactiveInstance;
var myLabelNew.BindingContext = myReactiveInstance;
var myLabelOld.SetBinding (Label.TextProperty, "StringField");
var myLabelNew.SetBinding (Label.TextProperty, nameof(ReactiveType.StringField));

Два вызова SetBinding передают идентичные значения: nameof(ReactiveType.StringField) это "StringField"не "ReactiveType.StringField" так, как вы могли бы первоначально ожидать.

Оператор с условным значением NULL

Более ранние обновления C# представили понятия типов, допускающих значение NULL, и оператор ?? объединения null, чтобы уменьшить объем стандартного кода при обработке значений, допускающих значение NULL. C# 6 продолжает эту тему с помощью оператора null-условного ?.. При использовании в объекте справа от выражения оператор null-условный возвращает значение члена, если объект отсутствует null и null в противном случае:

var ss = new string[] { "Foo", null };
var length0 = ss [0]?.Length; // 3
var length1 = ss [1]?.Length; // null
var lengths = ss.Select (s => s?.Length ?? 0); //[3, 0]

(Оба length0 и length1 выводятся для типа int?)

Последняя строка в предыдущем примере показывает ? оператор null-условный в сочетании с оператором ?? объединения NULL. Новый оператор C# 6 null-условный возвращается null на 2-м элементе в массиве, в то время как оператор объединения NULL выполняется и предоставляет 0 lengths массиву (независимо от того, подходит ли это, конечно, проблема).

Оператор null-условный должен значительно сократить количество стандартных значений null-проверка, необходимых во многих приложениях.

Существуют некоторые ограничения для оператора null-условного из-за неоднозначности. Вы не можете немедленно следовать списку ? аргументов с скобками, так как вы можете надеяться сделать с делегатом:

SomeDelegate?("Some Argument") // Not allowed

Invoke Однако можно использовать для разделения ? списка аргументов и по-прежнему является заметным улучшением по сравнению с блоком null-проверка шаблоны:

public event EventHandler HandoffOccurred;
public override bool ContinueUserActivity (UIApplication application, NSUserActivity userActivity, UIApplicationRestorationHandler completionHandler)
{
    HandoffOccurred?.Invoke (this, userActivity.UserInfo);
    return true;
}

Интерполяция строк

Функция String.Format традиционно использует индексы в качестве заполнителей в строке формата, например String.Format("Expected: {0} Received: {1}.", expected, received). Конечно, добавление нового значения всегда включало раздражающую небольшую задачу подсчета аргументов, переупорядочение заполнителей и вставку нового аргумента в правой последовательности в списке аргументов.

Новая функция интерполяции строк C# 6 значительно улучшается String.Format. Теперь можно напрямую присвоить переменным имя в строковом префиксе с помощью $. Например:

$"Expected: {expected} Received: {received}."

Переменные, конечно, проверка, а недоступная или недоступная переменная приведет к ошибке компилятора.

Заполнители не должны быть простыми переменными, они могут быть любым выражением. В этих заполнителях можно использовать кавычки, не экранируя эти кавычки. Например, обратите внимание на следующее "s" :

var s = $"Timestamp: {DateTime.Now.ToString ("s", System.Globalization.CultureInfo.InvariantCulture )}"

Интерполяция строк поддерживает синтаксис String.Formatвыравнивания и форматирования. Так же, как вы ранее писали {index, alignment:format}, в C# 6 вы пишете {placeholder, alignment:format}:

using static System.Linq.Enumerable;
using System;

class Program
{
    static void Main ()
    {
        var values = new int[] { 1, 2, 3, 4, 12, 123456 };
        foreach (var s in values.Select (i => $"The value is { i,10:N2}.")) {
            Console.WriteLine (s);
        }
    Console.WriteLine ($"Minimum is { values.Min(i => i):N2}.");
    }
}

дает следующий результат:

The value is       1.00.
The value is       2.00.
The value is       3.00.
The value is       4.00.
The value is      12.00.
The value is 123,456.00.
Minimum is 1.00.

Интерполяция строк является синтаксическим сахаром для String.Format: его нельзя использовать с @"" строковыми литералами и несовместим с const, даже если заполнители не используются:

const string s = $"Foo"; //Error : const requires value

В обычном случае создания аргументов функций с интерполяцией строк вам по-прежнему необходимо быть осторожным в отношении экранирования, кодирования и региональных параметров. Sql и URL-запросы, конечно, критически важны для очистки. Как и в String.Formatслучае с интерполяцией строк, используется CultureInfo.CurrentCulture. Использование CultureInfo.InvariantCulture немного больше слов:

Thread.CurrentThread.CurrentCulture  = new CultureInfo ("de");
Console.WriteLine ($"Today is: {DateTime.Now}"); //"21.05.2015 13:52:51"
Console.WriteLine ($"Today is: {DateTime.Now.ToString(CultureInfo.InvariantCulture)}"); //"05/21/2015 13:52:51"

Инициализация

C# 6 предоставляет ряд кратких способов указания свойств, полей и элементов.

Автоматическое инициализация свойств

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

class ToDo
{
    public DateTime Due { get; set; } = DateTime.Now.AddDays(1);
    public DateTime Created { get; } = DateTime.Now;

В конструкторе можно задать значение автоматического свойства только для получения:

class ToDo
{
    public DateTime Due { get; set; } = DateTime.Now.AddDays(1);
    public DateTime Created { get; } = DateTime.Now;
    public string Description { get; }

    public ToDo (string description)
    {
        this.Description = description; //Can assign (only in constructor!)
    }

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

См. раздел Инициализаторы индекса.

В C# 6 представлены инициализаторы индексов, которые позволяют задать ключ и значение в типах с индексатором. Как правило, это для Dictionaryструктур данных стилей:

partial void ActivateHandoffClicked (WatchKit.WKInterfaceButton sender)
{
    var userInfo = new NSMutableDictionary {
        ["Created"] = NSDate.Now,
        ["Due"] = NSDate.Now.AddSeconds(60 * 60 * 24),
        ["Task"] = Description
    };
    UpdateUserActivity ("com.xamarin.ToDo.edit", userInfo, null);
    statusLabel.SetText ("Check phone");
}

Элементы функции, на основе выражений

Лямбда-функции обладают несколькими преимуществами, одним из которых является просто экономия места. Аналогичным образом члены класса с выражением позволяют небольшим функциям выражаться немного более кратко, чем было возможно в предыдущих версиях C# 6.

Члены функции, на основе выражения, используют синтаксис лямбда-стрелки, а не традиционный синтаксис блока:

public override string ToString () => $"{FirstName} {LastName}";

Обратите внимание, что синтаксис лямбда-стрелки не использует явный returnсинтаксис. Для возвращаемых voidфункций выражение должно также быть оператором:

public void Log(string message) => System.Console.WriteLine($"{DateTime.Now.ToString ("s", System.Globalization.CultureInfo.InvariantCulture )}: {message}");

Элементы с выражением по-прежнему подвергаются правилу, которое поддерживается для методов, async но не свойств:

//A method, so async is valid
public async Task DelayInSeconds(int seconds) => await Task.Delay(seconds * 1000);
//The following property will not compile
public async Task<int> LeisureHours => await Task.FromResult<char> (DateTime.Now.DayOfWeek.ToString().First()) == 'S' ? 16 : 5;

Исключения

Нет двух способов: обработка исключений трудно получить правильно. Новые функции в C# 6 делают обработку исключений более гибкой и согласованной.

Фильтры исключений

По определению исключения происходят в необычных обстоятельствах, и это может быть очень трудно подумать и код обо всех способах исключения определенного типа. C# 6 представляет возможность защиты обработчика выполнения с помощью фильтра, вычисляемого средой выполнения. Это делается путем when (bool) добавления шаблона после обычного catch(ExceptionType) объявления. В следующем примере фильтр различает ошибку синтаксического анализа, относящуюся к date параметру, а не к другим ошибкам синтаксического анализа.

public void ExceptionFilters(string aFloat, string date, string anInt)
{
    try
    {
        var f = Double.Parse(aFloat);
        var d = DateTime.Parse(date);
        var n = Int32.Parse(anInt);
    } catch (FormatException e) when (e.Message.IndexOf("DateTime") > -1) {
        Console.WriteLine ($"Problem parsing \"{nameof(date)}\" argument");
    } catch (FormatException x) {
        Console.WriteLine ("Problem parsing some other argument");
    }
}

ожидание в улове... Наконец...

Возможности async , представленные в C# 5, были игровыми изменениями для языка. В C# 5 await не допускается в catch и finally блоках, раздражает, учитывая значение async/await возможности. C# 6 удаляет это ограничение, что позволяет асинхронным результатам последовательно ожидаться через программу, как показано в следующем фрагменте кода:

async void SomeMethod()
{
    try {
        //...etc...
    } catch (Exception x) {
        var diagnosticData = await GenerateDiagnosticsAsync (x);
        Logger.log (diagnosticData);
    } finally {
        await someObject.FinalizeAsync ();
    }
}

Итоги

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