Pull to refresh

Новые возможности C#, которые можно ожидать в ближайшее время

Reading time 4 min
Views 30K


В апреле 2003-его года был выпущен C# 1.2 и с тех пор все версии имели только major версию.
И вот сейчас, если верить официальной страничке roslyn на github, в работе версии 7.1 и 7.2.

Для того, чтобы попробовать версию 7.1 необходимо установить pre-release Visual Studio. Скачать ее можно скачать с официального сайта

Зайдя в свойства решения можно выбрать используемую версию языка.



Далее я хочу рассказать о понравившихся мне новых фичах языка.

Асинхронный Main (планируется в версии 7.1)


В наше время очень часто встречаются программы, которые почти полностью асинхронные.
До сих пор Main мог быть void или int. И мог содержать в себе аргументы в виде массива строк. Сейчас добавятся несколько новых перегрузок:

public static Task Main();
public static Task<int> Main();
public static Task Main(string[] args);
public static Task<int> Main(string[] args);

Так как CLR не поддерживает асинхронные entrypoints, то компилятор сам создает boilerplate code (то есть автоматически вставляет какой-то дополнительный код, который позволяет привести void к Task)
Примерно такой код будет сгенерирован компилятором:

async Task<int> Main(string[] args) {
    // здесь какой-то ваш код
}
// этот метод сгенерируется «за кулисами» автоматически
int $GeneratedMain(string[] args) {
    return Main(args).GetAwaiter().GetResult();
}

Я как и многие ожидал этот функционал еще в 7-ой версии языка.

Литерал default (планируется в версии 7.1)


У Visual Basic и у C# возможности примерно одинаковые. Но в самих языках есть определенные различия. Скажем у C# есть null, а у VB.NET есть Nothing. Так вот, Nothing может конвертироваться в любой системный тип, представляя собой значение по умолчанию для этого типа. Понятно, что null этого не делает. Хотя, значением по умолчанию вполне может быть и null.

Сейчас уже есть и применяется выражение default(T). Рассмотрим пример его применения. Допустим, что у нас есть метод, который принимает массив строк в качестве параметра:

void SomeMethod(string[] args)
{
            
}

Можно выполнить метод и передать ему значение массива строк по умолчанию так:

SomeMethod(default(string[]));

Но зачем писать default(string[]), если можно просто написать default?

Вот, начиная с C# 7.1 и можно будет пользоваться литералом default. Что еще можно будет с ним сделать, кроме как передать значение по умолчанию в метод? Например, его можно использовать в качестве значения необязательного параметра:

void SomeMethod(string[] args = default)
{
            
}

или инициализировать переменную значением по умолчанию:

int i = default;

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

int i = 1; 
if (i == default) { }   // значением по умолчанию типа int является 0
if (i is default) { }     // точно такая же проверка

Что нельзя будет сделать? Следующие примеры того как литерал default использовать нельзя:

const int? y = default;  
if (default == default)
if (default is T) // оператор is нельзя использовать с default
var i = default
throw default
default as int; // 'as' может быть только reference тип

Readonly ref (планируется в версии 7.2)


При отправке структур в методы в качестве by value (по значению) происходит копирование объекта, что стоит ресурсов. А иногда бывает необходимость отправить только значение ссылочного объекта. В таком случае разработчики отправляют значение по ссылке ref для того, чтобы сэкономить память, но при этом пишут в комментариях что значение изменять нельзя. Особенно часто это происходит при математических операциях с объектами vector и matrix.

Понятный пример того что нас ждет:

    static Vector3 Add (ref readonly Vector3 v1, ref readonly Vector3 v2)
    {
        // так нельзя!
        v1 = default(Vector3);

        // и так нельзя!
        v1.X = 0;

        // так тоже нельзя!
        foo(ref v1.X);

        // а вот теперь нормально
        return new Vector3(v1.X +v2.X, v1.Y + v2.Y, v1.Z + v2.Z);
    }

Эта функция пока что в состоянии прототипа, но я уверен в ее необходимости. Одним из предлагаемых вариантов является использование ключевого слова in вместо ref readonly

    static Vector3 Add (in Vector3 v1, in Vector3 v2)
   {
       return new Vector3(v1.X +v2.X, v1.Y + v2.Y, v1.Z + v2.Z);
   }

Почему in, ведь ref readonly понятнее? А потому что in короче.

Методы интерфейсов по умолчанию (планируется в версии 8.0)


Эти методы мы увидим не так скоро.

Представьте себе, что есть какой-то класс или структура, которые реализуют интерфейс. И вот они смогут унаследовать реализацию одного метода из интерфейса(!) или должны будут реализовать свою версию этого метода.

Уже само понятие реализации в интерфейсе звучит довольно странно. Хотя у Java 8 есть методы интерфейсов по умолчанию, и разработчики использующие C# тоже захотели себе подобный функционал.

Этот функционал позволит автору API изменить код метода для всех уже существующих реализаций интерфейса.

На примере должно быть понятно. Допустим у нас есть такой вот интерфейс у которого метод SomeMethod содержит реализацию:

interface IA
{
 void SomeMethod() { WriteLine("Вызван SomeMethod интерфейса IA"); }
}

Теперь создадим класс, которые реализует интерфейс:

class C : IA { }

И создадим экземпляр интерфейса:

IA i = new C();

Теперь мы можем вызвать метод SomeMethod:

i.SomeMethod(); // выведет на экран "Вызван SomeMethod интерфейса IA"

Предположительные имена кортежей (планируется в версии 7.1)


Этот функционал позволит в некоторых случаях не указывать имена кортежей. При определенных условиях элементы кортежа могут получить предположительные имена.

Пример:

Вместо того, чтобы писать (f1: x.f1, f2: x?.f2) можно просто написать (x.f1, x?.f2).
Первый элемент кортежа в таком случчае получит имя f1, а второй f2
Сейчас же кортеж стал бы неименнованным (к элементам которого можно обратиться только по item1, item2 ...)

Предположительные имена особенно удобны при использовании кортежей в LINQ

// c и result содержат в себе элементы с именами f1 и f2
var result = list.Select(c => (c.f1, c.f2)).Where(t => t.f2 == 1);

Отрицательные стороны

Основным недостатком введения этого функционала является совместимость с C# 7.0. Пример:

Action y = () => M();
var t = (x: x, y);
t.y(); // ранее был бы выбран extension method y(), а сейчас будет вызвана лямбда

Но с этим небольшим нарушением совместимости можно смириться, так как период между выходом 7.0 и 7.1 будет небольшим.

Собственно, уже сейчас Visual Studio при использовании 7.0 предупреждает, что
Tuple element name 'y' is inferred. Please use language version 7.1 or greater to access an element by its inferred name.

Полный код примера:

class Program
{
   static void Main(string[] args)
   {
       string x = "demo";
       Action y = () => M();
       var t = (x: x, y);
       t.y(); // ранее был бы выбран extension method y(), а сейчас будет вызвана лямбда  
   }

   private static void M()
   {
       Console.WriteLine("M");
   }
}

public static class MyExtensions
{
   public static void y(this (string s, Action a) tu)
   {
       Console.WriteLine("extension method");
   }
}

Если вы используете .NET 4.6.2 и ниже, то для того чтобы попробовать пример вам необходимо установить NuGet пакет System.ValueTuple.
Tags:
Hubs:
+32
Comments 73
Comments Comments 73

Articles