Давайте рассмотрим и остальные возможности новой версии 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 версии. Все интересующиеся вопросы, прошу в комментарии или в личку. Я вам обязательно отвечу. Всем спасибо!
Комментарии (11)
sand14
10.03.2018 01:22Спасибо, отличный перечень в короткой статье минорных, но приятных возможностей C#7. При этом хотелось бы, чтобы в статье упоминался еще async Main() (появился, если не ошибаюсь, в C# 7.2).
Насколько понимаю, в предыдущих статьях об этом не говорилось, однако это достаточно важная возможность, когда асинхронным API пользуется консольное приложение, и ей самое место в статье про солянку минорных возможностей.
До этого было не очень здорово, когда приходилось бойлерплейтить код, позволяющий вызывать из Main асинхронное API.Veikedo
10.03.2018 07:03"Бойлерплейтить" — это добавить один метод MainAsync и дождаться его завершения в Main?
bogotoff
11.03.2018 00:43p.GetCoordinates(out var x, out var y);
Читабельность ломается, непонятно какой тип. Хотя в некоторых случаях наверное удобно так писать.
withkittens
11.03.2018 22:42> Out переменные
> У переменной будет значение по умолчанию.
Если я правильно понимаю, там будет то, что туда положит вызываемая функция (она же обязана присвоить значенияout
-параметрам перед выходом). Да, скорее всего там будут дефолты, но в целом, по идее, это необязательно.
Lexxxxx
12.03.2018 07:37Встает вопрос, а что если конвертирование строки в в переменную типа DateTime будет неудачным, а мы все равно попытаемся воспользоваться выходным значением?
У переменной будет значение по умолчанию.
Мне кажется это не совсем правда. В общем случае там будет значение, которым метод инициализировал переменную. И совсем не обязательно это будет дефолтное значение. Согласно спецификации C# использование ключевого слова out с параметром метода обязывает метод инициализировать значение переменной до возвращения значения, но не накладывает никаких ограничений на само значение для инициализации.
AgentFire
Я бы добавил Task vs ValueTask обсуждение.
DistortNeo
Ага. Когда я замерял производительность, Task всегда оказывался эффективнее, чем ValueTask.
Kordov
Напиши про это статью)
DistortNeo
И будет она называться «Отказываемся от Task и пишем собственный планировщик».
Реально, накладные расходы при асинхронных операциях с сокетами меня выбешивали. В итоге написал собственный однопоточный планировщик с IOPS на пару порядков выше.