Monday, September 15, 2014

Visual Studio "14" and C# 6.0

Сегодня в статье, посвященной языку C# 6.0, мы рассмотрим его возможности, которые уже доступны для ознакомления в Visual Studio "14", и CTP версию, которую можно скачать уже сейчас. На момент написания статьи была доступна третья версия Visual Studio "14". Рекомендую для ознакомления не ставить студию непосредственно на свой компьютер, а воспользоваться для этого виртуальной машиной. Это один из хороших подходов, когда нужно проверить что-то новое, зная при этом, что на его использование у вас есть мало времени. Один из минусов при установке Windows 8.1 на VMware player или Virtual Box: вы наверняка получите сообщение следующего вида:
VMware Player and Hyper-V are not compatible. Remove the Hyper-V role from the system before running VMware Player.
Я много времени потратил, чтобы устранить эту проблему. В BIOS я не нашел решения, но наткнулся на статью, которая подсказала, как это можно исправить. Нужно запустить командную строку под администратором и выполнить команду
bcdedit /set hypervisorlaunchtype off
После этого нужно перегрузить систему. Если вы проделаете эти действия, то сможете поставить Windows 8.1 на VMware player. Чтобы вернуть все как было, достаточно выполнить команду:
bcdedit /set hypervisorlaunchtype auto
После обеих операций необходимо перегрузить компьютер, чтобы изменения вступили в силу.
После того как вы установите Windows 8.1 на виртуальную машину (или реальную машину, в зависимости от вашего желания), следующим логичным шагом будет поставить Visual Studio "14", если вы хотите попробовать в действии новые возможности языка C# 6.0.
Примечание: в Visual Studio "14" при создании нового проекта уже доступны возможности нового C# 6.0, но для того чтобы попробовать те возможности, которые еще полностью не протестированы, или те, которые добавились, необходимо немного изменить файл проекта. Для этого нужно открыть проект в блокноте и изменить его, как показано на рисунке ниже.

Ни в коем случае не ставьте Visual Studio "14" с другими студиями, так как есть большая вероятность, что их работа сломается.
Давайте создадим в нашей студии новое приложение и будем последовательно изучать возможности будущей версии C#. Назовем наше приложение для примера "NewCsharpOpportunitySample", как показано на рисунке ниже.

Затем закроем наш проект в студии и откроем папку, в которой создан наш проект; ищем файл проекта с расширением .csproj и открываем это файл в блокноте. После строки <WarningLevel>4</ WarningLevel> добавляем строку <LangVersion>experimental</LangVersion>. Слово experimental должно быть в нижнем регистре, иначе у вас не заработают новые возможности языка C#.

Использование using для статических типов
Напишем простую программу для вывода на консоль классического предложения "Hello World". Вот как это выглядело бы в классическом C# 5.0:
static void Main(string[] args)
{
    Console.WriteLine("Hello World");
    Console.ReadLine();
}
Теперь посмотрим, как изменится наш пример, если мы воспользуемся новой возможностью указывать using для static классов.
using System.Console;

namespace ConsoleApplication
{
    class Program
    {
        static void Main(string[] args)
        {
            WriteLine("Hello World");
            ReadLine();
        }
    }
}
Мы можем запустить наш проект и убедиться, что он работает.
Благодаря проекту Roslyn вы можете сворачивать ваш код в Visual Studio "14" и смотреть, какой код у вас написан.
Эта возможность пока реализована кривовато, так как постоянно подвисает. У меня был случай, когда зависла просто свернутая функция, и я никак не мог развернуть ее обратно.
Для такого же эффекта, как с классом System.Console, мы можем работать с библиотекой Math и другими статическими классами. Считаю это очень неплохой возможностью.
Для практики использования этих возможностей посмотрим на простой пример с возможностью, которую мы рассмотрели выше с использованием using для статических классов, остальную часть напишем на C# 5.0 и будем постепенно его переписывать под C# 6.0. 
class Program
{
    static void Main(string[] args)
    {
        var point = new Point(2.0, 4.0);
        WriteLine("X position {0}, Y position {1}", point.X, point.Y);
        ReadLine();
    }

}

public class Point
{
    public Point(double x, double y)
    {
        _x = x;
        _y = y;
    }

    private readonly double _x;
    public double X
    {
        get { return _x; }
    }

    private readonly double _y;
    public double Y
    {
        get { return _y; }
    }
}
Затем рассмотрим, как в этом примере использовать новый конструктор, который стал доступен в новом языке.

Primary Constructor
Посмотрите, как будет выглядеть класс Point после изменений.
public class Point(double x, double y)
{
    private readonly double _x = x;
    public double X
    {
        get { return _x; }
    }

    private readonly double _y = y;
    public double Y
    {
        get { return _y; }
    }
}
Этот код полностью рабочий. Мне был по душе синтаксис C# 5 с основным конструктором. Но разработчики Microsoft решили, что будет неплохо добавить такую возможность. Полное описание данной возможности вы можете найти по данной ссылке: C# Language Design Notes for Apr 21, 2014 (Part I).
Следующим логическим шагом рекомендую рассмотреть неизменяемые автопроперти.

Read only auto properties
Теперь еще немного модифицируем наш класс Point с предыдущего абзаца и посмотрим, как будет изменен наш код.
public class Point(double x, double y)
{
    public double X { get; } = x;
    public double Y { get; } = y;
}
Этот синтаксис мне нравится намного больше предыдущего, так как код становится более читабельным, компактным и по сути интуитивно понятным.
В новой версии C# разработки много потрудились над использованием лямбда-выражений. Новый язык нам позволяет использовать свойства и методы, используя лямбда-выражения. Для примера я объединил два пункта.

Method & Property Expression
Понятное дело, что мы и дальше будем мучить наш класс Point, поэтому не отклоняясь от темы, представляю вашему вниманию использование лямбда-выражения для создания проперти и метода.
public class Point(double x, double y)
{
    public double X { get; } = x;
    public double Y { get; } = y;

    //Property Expression
    public double Distance => Math.Sqrt((X * X) + (Y * Y));

    //Method Expression
    public Point Move(int dx, int dy) => new Point(X + dx, Y + dy);
}

Ключевое слово nameof
В сам язык добавили новый оператор, который позволяет получить полное имя типа. Более полное описание приведено на английском ниже.
All members are treated as if they were static members. This means that instance members are accessed by dotting off the type rather than an instance expression. It also means that the accessibility rules around protected instance members are the simpler rules that apply to static members.
Generic types are recognized by name only. Normally there needs to be a type parameter list (or at least dimension specifier) to disambiguate, but type parameter lists or dimension specifiers are not needed, and in fact not allowed, on the rightmost identifier in a nameof.
Ambiguities are not an error. Even if multiple entities with the same name are found, nameof will succeed. For instance, if a property named M is inherited through one interface and a method named M is inherited through another, the usual ambiguity error will not occur.
Полное описание данного ключевого слова вы можете прочесть здесь: C# Language Design Notes for Jul 9, 2014, а мы посмотрим пример его использования.
class Program
{
    static void Main(string[] args)
    {
        var point = new Point(2.0, 4.0);
        WriteLine(nameof(point));
        ReadLine();
    }

}
Результат показан на экране ниже.
Этот оператор по поведению подобен оператору typeof.
Мне по душе больше пришлось решение, которое увидел на stackoverflow.
public class Nameof<T>
{
    public static string Property<TProp>(Expression<Func<T, TProp>> expression)
    {
        var body = expression.Body as MemberExpression;
        if (body == null)
            throw new ArgumentException("'expression' should be a member expression");
        return body.Member.Name;
    }
}
Использование:
WriteLine(Nameof<Point>.Property(e => e.Distance));
Второй подход с деревьями выражений выглядит более наглядно, и в отличие от оператора nameof уже может быть использован у вас в коде.

Inline declaration for out params
Теперь для out параметров мы можем встраивать объявление переменной. Давайте посмотрим, как это выглядит на примере классической функции TryParse.
static void Main(string[] args)
{
    int.TryParse("123", out int x);
    WriteLine(x);
    ReadLine();
}  
Это неплохой способ избежать километровых объявлений переменных перед вызовом метода с out параметрами, что улучшит читаемость кода.

Null propagation operator associativity
У ребят с Редмонда давно была идея реализовать этот оператор. Он является, по сути, реализацией монады Maybe, которая довольно популярна в функциональном программировании. Давайте посмотрим все тот же пример с использованием реализованного нами класса Point, но в разрезе использования данного оператора.
public static double GetXCoordinate(List<Point> points)
{
    if (points != null)
    {
        var next = points.FirstOrDefault();
        if (next != null && next.X != null) return next.X;
    }
    return -1;
}
Реализация с помощью null propagation operator выглядит так:
public static double GetXCoordinate(List<Point> points)
{
    return points?.FirstOrDefault()?.X ?? -1;
}

Await Calls from Within a Catch/Finally Block
Появилась возможность использовать ключевое слово await в catch блоке. Пример:
class Program
{
    static void Main(string[] args)
    {
        SampleAsyncMethod();
        ReadLine();
    }

    public static async void SampleAsyncMethod()
    {
        try
        {
            string result = await WaitAsynchronouslyAsync();
            WriteLine(result);
        }
        catch(Exception ex)
        {
            WriteLine(await WaitAsynchronouslyException());
        }

    }
    public static async Task<string> WaitAsynchronouslyAsync()
    {
        await Task.Delay(10000);
        throw new Exception("error");
        return "Finished";
    }

    public static async Task<string> WaitAsynchronouslyException()
    {
        await Task.Delay(1000);
        return "Exception";
    }
}
Правда, сложно представить ситуацию, когда в блоке catch может понадобиться асинхронный вызов методов. Постараюсь в ближайшее время рассмотреть если у меня выйдет, как это реализовано на уровне IL кода. В целом, данное улучшение довольно-таки неплохое.

Leveraging Exception Filters to Pinpoint Which Exception to Catch
Одно из хороших обновлений, которые появились в новом C#, − это фильтровать некоторые типы ошибок. Например, если вы используете возможности Win32 Api, то вы, наверное, сталкивались с тем, что некоторые коды ошибок не являются критическими или вообще не являются ошибками. Такие ошибки неплохо уметь обрабатывать. Новый C# предоставляет нам эту возможность. Ниже вы можете посмотреть на рабочий пример, как это можно использовать в своем приложении.
Пример:
private static void TestException()
{
    try
    {
        throw new Win32Exception(Marshal.GetLastWin32Error());
    }
    catch (Win32Exception exception)
        if (exception.NativeErrorCode == 0x00042)
    {
        // Only provided for elucidation (not required).
    }
}
Или более простой пример с проверкой своих событий:
try
{
    throw new Exception("Me");
}
catch (Exception ex) if (ex.Message == "You")
{
    // this one will not execute.
}
catch (Exception ex) if (ex.Message == "Me")
{
    // this one will execute
}

Parameterless constructor in structs
Это нововведение разработчики Microsoft презентовали совсем недавно C# Language Design Notes for Aug 27, 2014 (Part II)Оно касается того, что в структуры будет добавлен конструктор по умолчанию. Расскажу вначале, чем это грозит для разработчиков, так как это ломает обратную совместимость. Конструкторов по умолчанию у структур нет, а вызов new S() равносильно вызову default(S). Теперь эта логика будет отрабатывать по-другому. default(S) будет использоваться для инициализации массивов, а new S() будет приводить с вызову конструктора по умолчанию. И сейчас структуры по синтаксису очень похожи на классы.
public 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;
}
Давайте посмотрим на довольно забавный случай использования конструктора по умолчанию.
class C(Action subscriber)
{
    public event Action SomethingHappened = subscriber;
}
Мне кажется немного странным, что приведённый выше код нельзя переписать следующим образом:
public event Action SomethingHappened
{ add { } remove { } } = subscriber;

Primary constructor body
Здесь достаточно показать простой пример, чтобы увидеть какие произошли изменения. Пример взять с предварительного описания новых возможностей языка C#.
public class Customer(string first, string last)
{
    {
        if (first == null) throw new ArgumentNullException("first");
        if (last == null) throw new ArgumentNullException("last");
    }
    public string First { get; } = first;
    public string Last { get; } = last;
}

Dictionary initializer
Для словарей добавили новый тип инициализатора с помощью индексаторов. К сожалению, данный синтаксис на время отключен с последней CTP3 версии, но в предыдущей версии он работает. Так что надеюсь, что данный способ инициализации не уберут из языка C#, как убрали indexer member initializer.
var numbers = new Dictionary<string, int> {
    ["one"] = 1,
    ["two"] = 2
};
А вот какой синтаксис был с indexer member initializer на примере работы с json:
var point = new JObject { $x = 3, $y = 7 };
Основная причина такого отказа − нечитабельный синтаксис, а также то, что эта идея не понравилась сообществу разработчиков. Вот что об этом сказал Мадс Торгерсен:
"Well pull the feature. There’s little love for it, and we shouldn’t litter the language unnecessarily. If this causes an outcry, well that’s different feedback, and we can then act on that."
Теперь немного интересных фактов, которыми разработчики языка C# любезно с нами поделились. Сначала разработчики C# хотели добавить private protected.
Вот описание дискуссии о том, как это должно было выглядеть: "What is the meaning of the planned “private protected” C# access modifier?". Ниже приведен пример уровней доступа с диаграммами Венна, взятые со stackoverflow.
private:
private protected: ( suggested feature - currently not available in C#)
internal:
protected:
protected internal:
public:

В ближайшее время в язык обещают добавить разделитель для чисел и бинарные литералы. Так что нас еще ждет очень много всяких вкусностей языка. Надеюсь, что данный обзор показался вам не очень скучным. Как только ребята с Microsoft выпустят новую версию, постараюсь рассмотреть ее вместе с вами. Рекомендую при возможности установить себе Visual Studio "14", попробовать возможности языка C# 6.0 и быть постоянно на гребне волны, чтобы всегда оставаться в тренде.