Нам осталось рассмотреть последнюю по приоритету, но не по важности операцию присваивания. Синтаксически присваивание в языке C# является частным случаем выражения и может встречаться всюду, где разрешено появление выражений. Выражение присваивания является выражением с побочным эффектом. Оно не только возвращает значение некоторого типа в качестве результата, но и изменяет значения одной или нескольких переменных в качестве побочного эффекта. В реальных программах присваивание чаще всего встречается не как выражение, а как оператор присваивания. Превратить выражение присваивания в оператор присваивания проще простого. Нужно выражение закончить символом точка с запятой и использовать его там, где разрешается использовать операторы.
Начнем с формального определения синтаксиса выражения присваивания:
<переменная> <знак присваивания> <выражение>
Знаков присваивания много, они перечислены в таблице 3.1. Чаще всего используется знак равенства, но иногда ему могут предшествовать и другие знаки операций. С чем связано наличие многих знаков у одной операции? Языку C# это досталось в наследство от языка С++, где авторы языка были большими любителями краткости записи в ущерб ее ясности. Поэтому в языке допустимы такие выражения, как x++, x+=y, мало понятные обычному математику. Второе из этих выражений является выражением присваивания и удовлетворяет выше приведенному синтаксису со знаком присваивания +=. Его можно рассматривать как краткую запись выражения . Аналогичный смысл имеют и другие знаки присваивания - (*=, /= и другие).
В правой части синтаксической формулы, определяющей выражение присваивания, стоит выражение, которое может быть в свою очередь выражением присваивания. Отсюда следует допустимость множественного присваивания. Синтаксически вполне корректен следующий пример:
/// <summary> /// Множественное присваивание /// </summary> static void AssignTest() { double x = 1, y = 2, z = 3, w =9, u = 7, v = 5; if((x += y -= z += w *= (u + v) / (u - v)) < 0) Console.WriteLine("x = {0}, y = {1}, z = {2}," + "w = {3}, u = {{4}, v = {5}", x, y, z, w, u, v); }
В операторе if записано выражение, задающее множественное присваивание. Какова семантика, как вычисляются выражения присваивания?
Операция присваивания является правосторонней операцией, и особенностью вычисления выражения присваивания является то, что оно вычисляется справа налево. В нашем примере вначале будет вычислено самое правое выражение (u + v) / (u - v), значение которого будет равно 6. Двигаясь налево по ходу присваивания, значение выражения будет изменяться. Последним будет вычислено выражение, которое получит переменная x. Значение этого выражения равно -54, именно оно является окончательным значением выражения множественного присваивания и будет участвовать в сравнении с нулем. Условие в операторе if получит значение true, и метод WriteLine выведет на консоль значения переменных, полученных ими как побочный результат вычисления выражения присваивания. Эти значения соответственно равны: -54, -55, 57, 54, 7, 5. Заметьте, скобки, окружающие выражение присваивания, необходимы, иначе операция сравнения выполнялась бы раньше, чем присваивание, что приводило бы к ошибке.
Для пояснения деталей семантики выражений присваивания использована довольно экзотическая конструкция в операторе if. В реальных программах такие конструкции применять не следует. Они "от лукавого". Простота записи и понимаемость - одни из главных критериев при создании промышленного кода. При изучении возможностей языка допустимо рассмотрение экзотических случаев.
1.10. Операция ?? - новая операция C# 2.0
Эта операция уже рассматривалась в предыдущей лекции, когда речь шла о типах, допускающих значение null. Напомню, все ссылочные типы изначально допускают null в качестве возможного значения. Такое значение ссылочной переменной задает неопределенную ссылку, ссылку на несуществующий объект. Значимые типы значения null не содержат, но можно определить расширенный значимый тип, включающий значение null. Синтаксически, если T - имя значимого типа, то T? - это имя расширенного типа. Операция ?? определена над операндами, допускающими значение null. Ее главная задача - присвоить переменной значение, отличное от null, поэтому иногда ее называют операцией склеивания, поскольку она позволяет "приклеить" к null значение. Рассмотрим ее определение:
A ?? B
Если операнд A отличен от null, то он и возвращается в качества результата операции. Если же он имеет значение null, то результатом является операнд B. Эту операцию особенно удобно использовать при приведении типа T? к типу T. Рассмотрим простой пример:
int? x = null;…int y = x ?? 0;
Заметьте, если между двумя присваиваниями переменная x не приобрела значение, отличное от null, то переменная y в результате получит значение 0.
В отсутствии такой операции нам пришлось бы писать для вычисления у такую эквивалентную конструкцию:
int y = (x !=null) ? (int)x : 0
1.11. Лямбда-оператор - новая операция в C# 3.0
В третьей версии языка появилась новая операция, называемая лямбда-оператором, и, соответственно, новый тип выражений, называемых лямбда-выражениями. Эта операция имеет тот же приоритет, что и операция присваивания, и, так же как и последняя, является правосторонней операцией. Синтаксис лямбда-выражений следующий:
<(список входных аргументов)> => <выражение>
Содержательно выражение в правой части задает описание функции, аргументы которой задаются списком левой части. Такое описание представляет собой описание анонимной функции - функции без имени - и может быть использовано, например, при задании экземпляра делегата. Зачастую функция зависит от одного аргумента, и тогда в левой части можно указывать только имя этого аргумента, опуская скобки.
Подробно этот механизм будет рассмотрен в отдельной лекции нашего курса, а пока приведем первый простой пример использования этой операции. Рассмотрим следующую задачу. Пусть дан массив чисел X и задана функция F(x). Требуется найти минимальное значение этой функции, когда аргументы задаются элементами массива X. Конечно же, можно создать метод, реализующий вычисление функции F(x), но можно воспользоваться анонимной функцией, заданной лямбда-выражением, что демонстрирует следующий пример:
/// <summary> /// Лямбда-оператор и лямбда-выражение /// </summary> static void Lambda() { Random rnd = new Random(); const int size = 5; int[] numbers = new int[size]; int a = rnd.Next(-10, 10), b = rnd.Next(-10, 10), c = rnd.Next(-10, 10); Console.WriteLine("a={0}, b={1}, c={2}", a, b, c); for (int i = 0; i < size; i++) { numbers[i] = rnd.Next(10); Console.Write("X[{0}] = {1}, ", i, numbers[i]); } int minValue = numbers.Min(x => a * x * x + b * x + c); Console.WriteLine("Min(a*x^2 +b*x + c) = {0}", minValue); }
Большая часть в этом примере связана с моделированием массива чисел и коэффициентов функции. Нахождение минимума функции задается одной строкой:
int minValue = numbers.Min(x => a * x * x + b * x + c);
Здесь функция Min последовательно перебирает элементы массива, формируя аргумент x функции, а лямбда-выражение преобразует его в значение функции от этого аргумента. В результате возвращается минимальное значение функции. Результаты работы метода Lambda можно видеть на рис. 3.5.
увеличить изображение Рис. 3.5. Результаты работы метода Lambda
На этом закончим рассмотрение операций языка C#, но продолжим разговор о некоторых вопросах, связанных с вычислением выражений.