Уже прошло достаточно времени после выпуска C# версии 7. Visual Studio 2017 также стала достаточно стабильной. Думаю, можно начинать использовать возможности седьмой версии в реальных проектах. Давайте посмотрим, что же нового было добавлено.
Out-переменные (out variables)
Данное нововведение позволяет не объявлять out переменные заранее, до их непосредственной передачи в качестве аргумента в метод.
Например, у нас есть следующих код:
static void Main(string[] args)
{
string firstName;
string lastName;
GetUserInfo(out firstName, out lastName);
Console.WriteLine($"{firstName} {lastName}");
}
public static void GetUserInfo(out string firstName, out string lastName)
{
firstName = "Андрей";
lastName = "Амельченя";
}
Метод GetUserInfo позволяет получить какую-то информацию о пользователе, записав её в out переменные.
В C# 7 мы можем не объявлять переменные заранее и написать так:
static void Main(string[] args)
{
GetUserInfo(out string firstName, out string lastName);
Console.WriteLine($"{firstName} {lastName}");
}
public static void GetUserInfo(out string firstName, out string lastName)
{
firstName = "Андрей";
lastName = "Амельченя";
}
Указывать конкретный тип не обязательно, мы можем использовать var, как при объявлении переменных
GetUserInfo(out var firstName, out var lastName);
Сопоставление с образцом (Pattern matching)
Данный функционал уже давно ждали. Многие писали свою реализацию pattern matching, но вот, наконец-то, в C# это добавили официально.
Чтобы лучше понять, рассмотрим следующий код, в котором новые возможности еще не используются:
public class User
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class MobilePhone
{
public string Phone { get; set; }
}
class Program
{
public static string GetInfo(object o)
{
if (o == null) return null;
if (o is User)
{
var user = (User)o;
return user.FirstName;
}
if (o is MobilePhone)
{
var phone = (MobilePhone)o;
return phone.Phone;
}
if (o is int)
{
var age = (int)o;
return $"{age * 365} дней";
}
return String.Empty;
}
static void Main(string[] args)
{
var user = new User() {FirstName = "Андрей", LastName = "Амельченя"};
var phone = new MobilePhone() {Phone = "+79150000000"};
Console.WriteLine(GetInfo(user));
Console.WriteLine(GetInfo(phone));
Console.WriteLine(GetInfo(27));
}
}
Используя возможности C#7 метод GetInfo примет вид:
public static string GetInfo(object o)
{
if (o == null) return null;
if (o is User user)
{
return user.FirstName;
}
if (o is MobilePhone phone)
{
return phone.Phone;
}
if (o is int age)
{
return (age * 365).ToString();
}
return String.Empty;
}
Также можно воспользоваться switch. Используя ключевое слово when можно добавить дополнительные проверки
public static string GetInfo(object o)
{
if (o == null) return null;
switch (o)
{
case User user when user.FirstName == "Root":
return null;
case User user:
return user.FirstName;
case MobilePhone phone:
return phone.Phone;
case int age when age > 100:
return "Максимальный возраст превышен";
case int age:
return $"{age * 365} дней";
}
return String.Empty;
}
Кортежи (tuples)
Наконец-то с кортежами стало удобно работать! Рассмотрите код, как приходилось писать раньше:
public static Tuple<int, int, int> GetTime()
{
return new Tuple<int, int, int>(14, 15, 0);
}
static void Main(string[] args)
{
var time = GetTime();
Console.WriteLine($"{time.Item1}:{time.Item2}:{time.Item3}");
}
Обилие этих Item1, Item2…порой подбишивало, ведь в них легко запутаться.
Сейчас данный код можно свести к этому:
public static (int hour, int minutes, int seconds) GetTime()
{
return (14, 15, 0);
}
static void Main(string[] args)
{
var time = GetTime();
Console.WriteLine($"{time.hour}:{time.minutes}:{time.seconds}");
}
Но нужно не забыть подключить библиотеку System.ValueTuple
Дополнительно, мы легко можем произвести деконструкцию кортежа в локальные переменные, делается это так:
var (hour, minutes, seconds) = GetTime();
Console.WriteLine($"{hour}:{minutes}:{seconds}");
Локальные методы (local functions)
Вообще использовать локальные методы можно было и раньше, использую ламбда-переменные. Например, вот в таком виде:
var length = new Func<int, int, int, int, double>((x1,y1, x2, y2) => Math.Sqrt(Math.Pow(x1-x2,2)+Math.Pow(y1-y2,2)));
Console.WriteLine(length(0,0,5,5));
Но в версии 7 это стало удобнее:
double Length(int x1, int y1, int x2, int y2)
{
return Math.Sqrt(Math.Pow(x1 - x2, 2) + Math.Pow(y1 - y2, 2));
}
Console.WriteLine(Length(0, 0, 5, 5));
Еще стоит обратить внимание на то, что локальная функция может располагаться где угодно в методе. Её можно сначала вызывать, а уже потом объявлять:
Console.WriteLine(Length(0, 0, 5, 5));
double Length(int x1, int y1, int x2, int y2)
{
return Math.Sqrt(Math.Pow(x1 - x2, 2) + Math.Pow(y1 - y2, 2));
}
Здесь главное использовать это без фанатизма, когда действительно необходимо.
Возвращаемые значения по ссылке (ref returns)
Раньше значения значимого типа можно было передавать в методы по ссылке, используя ключевое слово ref. В седьмой версии добавлена возможность возвращать значения по ссылке:
static void Main(string[] args)
{
var a = new[] {1, 2, 3, 4, 5};
ref int element = ref FindElement(3, a);
element = -50;
Console.WriteLine(a[2]); //-50
}
public static ref int FindElement(int value, int[] array)
{
for (int i = 0; i < array.Length; i++)
{
if (array[i] == value)
{
return ref array[i];
}
}
throw new IndexOutOfRangeException("Value not found");
}
Бинарные литералы (binary literals)
Сейчас можно задавать бинарные значения в удобном виде непосредственно сразу в коде, записав 0b а затем бинарное значение, например:
var b = 0b100;//4
Также сейчас можно разделять цифры любым количеством подчеркиваний, если вам так удобнее, например:
var x = 100_000_000.0___01;
var y = 0b1000_0000_0000;
Лично мне нравится путь развития C#. В его добавляют всё больше функциональных возможностей и делают его удобнее для работы. В данном релизе я очень рад добавлению Pattern matching и изменению работы с кортежами. В общем, C# 7 уже можно использовать в product проектах, а пока ждём восьмую версию:)
Приятного программирования.