Исследуем минорные возможности C# 7
C# 7 добавляет ряд новых возможностей и фокусирует внимание на потреблении данных, упрощении кода и производительности. Самые большие фичи были уже рассмотрены — кортежи, локальные функции, сопоставление с образцом и throw выражения. Но есть и другие новые возможности, как большие, так и малые. Все они сочетаются, чтобы сделать код более эффективным и понятным, чтобы все были счастливы и продуктивны.
Давайте рассмотрим и остальные возможности новой версии C#. Итак! Начнем!
Out переменные
В настоящее время в C# использование out параметров не так гибко, как хотелось бы. Прежде чем вызвать метод с out параметрами, сначала нужно объявить переменные для передачи методу. Поскольку обычно эти переменные не инициализируются (так как они будут переписаны методом), то нельзя использовать var для их объявления, следовательно обязательно нужно указать полный тип:
public void PrintCoordinates(Point p)
{
int x, y; // have to "predeclare"
p.GetCoordinates(out x, out y);
WriteLine($"({x}, {y})");
}
В C# 7 вводятся out переменные. Они позволяют объявлять переменные прямо на месте, где они передаются как out аргументы:
public void PrintCoordinates(Point p)
{
p.GetCoordinates(out int x, out int y);
WriteLine($"({x}, {y})");
}
Обратите внимание, что переменные поднимаются во внешнюю область видимости в закрывающем блоке, поэтому последующая строка может их использовать.
Поскольку out переменные объявляются непосредственно в качестве аргументов out параметров, компилятор может вывести их тип (если нет противоречивых перегрузок), поэтому можно использовать var вместо типа при объявлении:
p.GetCoordinates(out var x, out var y);
Обычно out параметры используются в шаблонах Try…, где логическое возвращаемое значение указывает на успех, а параметры out несут полученные результаты:
public void PrintStars(string s)
{
if (int.TryParse(s, out var i)) { WriteLine(new string('*', i)); }
else { WriteLine("Cloudy - no stars tonight!"); }
}
Можно также использовать »wildcards» в out параметрах в виде _, чтобы игнорировать out параметры, которые не нужны:
p.GetCoordinates(out int x, out _); // I only care about x
Обратите внимание, что можно опустить объявление типа при »wildcards».
string inputDate = "";
if (DateTime.TryParse(inputDate, out DateTime dt))
{
// используем dt
}
Встает вопрос, а что если конвертирование строки в в переменную типа DateTime будет неудачным, а мы все равно попытаемся воспользоваться выходным значением?
У переменной будет значение по умолчанию.
Улучшения литералов
C# 7 позволяет использовать символ »_» в качестве разделителя цифр внутри числовых литералов:
var d = 123_456;
var x = 0xAB_CD_EF;
Можно помещать данный символ в любом месте между цифрами нужное количество раз, чтобы улучшить читаемость. Он не влияет на значение.
Разделитель разрядов можно использовать с типами byte, int, long, decimal, float и double:
public const long BillionsAndBillions = 100_000_000_000;
public const double AvogadroConstant = 6.022_140_857_747_474e23;
public const decimal GoldenRatio = 1.618_033_988_749_894_848_204_586_834_365_638_117_720_309_179M;
Кроме того, C# 7 представлены бинарные литералы. Теперь можно записать число в бинарном виде.
var b = 0b101010111100110111101111;
0b в начале константы означает, что число записано в двоичном формате.
Также можно использовать знак разделителя разрядов, что несомненно улучшит читаемость литерала.
var b = 0b1010_1011_1100_1101_1110_1111;
Разделитель разряда не может стоят в конце или в начале литерала
byte a = 0b_0000_0001; //INVALID: Digit separator cannot be at the start or end of the value
byte b = 0b1_0000_0001; //INVALID: 257 doesn't fit in a byte
byte c = 0b0_0000_0001; //VALID: 1 fits into a byte just fine
Ref returns and locals
Так же, как можно передавать параметры метода по ссылке (с модификатором ref), теперь также можно вернуть их по ссылке, а также сохранить их по ссылке в локальных переменных.
public ref int Find(int number, int[] numbers)
{
for (int i = 0; i < numbers.Length; i++)
{
if (numbers[i] == number)
{
return ref numbers[i]; // return the storage location, not the value
}
}
throw new IndexOutOfRangeException($"{nameof(number)} not found");
}
int[] array = { 1, 15, -39, 0, 7, 14, -12 };
ref int place = ref Find(7, array); // aliases 7's place in the array
place = 9; // replaces 7 with 9 in the array
WriteLine(array[4]); // prints 9
Это полезно для возвращения конкретных полей из больших структур данных. Например, игра может содержать свои данные в большом предварительно распределенном массиве структур (чтобы избежать пауз при сборке мусора). Теперь методы могут возвращать ссылку непосредственно на такую структуру, через которую вызывающий метод может ее читать и модифицировать.
Существуют некоторые ограничения для обеспечения безопасности:
- Вы можете возвращать только те ссылки, которые «безопасны для возврата»: только те, которые были переданы вам, и те, которые указывают на поля в объектах
- Локальные ref переменные инициализируются в определенном месте хранения и не могут быть изменены, чтобы указывать на другой
Возвращаемые обобщенные типы в async методах
До сих пор async методы в C# должны либо возвращать void, Task или Task‹T›. C# 7 позволяет определять другие типы таким образом, чтобы их можно было возвращать из асинхронного метода. Возвращаемый тип должен по-прежнему соответствовать асинхронному шаблону, а значит, метод GetAwaiter должен быть доступен. Конкретный пример: в .NET Framework добавлен новый тип ValueTask, позволяющий использовать эту новую возможность языка
Поскольку Task и Task‹T› являются ссылочными типами, выделение памяти во влияющих на производительность сегментах (особенно при выделении памяти в ограниченных циклах) может серьезно снизить производительность. Поддержка обобщенных типов возвращаемых значений позволяет возвращать небольшой значимый тип вместо ссылочного типа, благодаря чему удается предотвратить избыточное выделение памяти.
class Program
{
static Random rnd;
static void Main()
{
Console.WriteLine($"You rolled {GetDiceRoll().Result}");
}
private static async ValueTask GetDiceRoll()
{
Console.WriteLine("...Shaking the dice...");
int roll1 = await Roll();
int roll2 = await Roll();
return roll1 + roll2;
}
private static async ValueTask Roll()
{
if (rnd == null)
rnd = new Random();
await Task.Delay(500);
int diceRoll = rnd.Next(1, 7);
return diceRoll;
}
}
Заключение
Используйте новые возможности языка C#, ведь они облегчают разработку, экономят время и повышают читаемость кода. Этой статьей заканчиваю цикл статей по C# 7 версии. Все интересующиеся вопросы, прошу в комментарии или в личку. Я вам обязательно отвечу. Всем спасибо!