Skip to content

Операторы со знаком вопроса: ??, ?. и ?:

edited March 2022 in Tutorials

Null-coalescing

?? - это Null-coalescing оператор. Он возвращает значение левого операнда, если оно не равно нулю. В противном случае возвращается значение правого.

string str=null, str2="test";
WriteLine(str ?? str2); //test

?? - настоящий оператор, а операторы ?. и ?: являются синтаксическим сахаром для функций IfNotNull(объект, значение) и If(условие, значение_1, значение_2).

Ternary

?: - условный (тернарный) оператор: условие ? true-выражение : false-выражение.

WriteLine(5>4 ? "hello" : "world"); //hello
WriteLine(5<4 ? "hello" : "world"); //world
//Теперь посмотрим, что видит интерпретатор. Для этого создадим функцией Expr() выражение.
WriteLine(Expr(5<4 ? "hello" : "world")); //@If(5<4,"hello","world")

Т.е. 5<4 ? "hello" : "world" - то же самое, что @If(5<4,"hello","world"). Символ @ перед именем функции указывает интерпретатору, что нужно вызвать базовую (встроенную) функцию, поэтому перегрузить операторы ?. и ?: не получится.
Этот оператор можно использовать без третьего аргумента.

WriteLine(5>4? 1); //1
WriteLine(5<4? 1); //0
WriteLine(Expr(5<4? 1)); //@If(5<4,1,)

WriteLine(5>4? Now()); //24.12.2021 12:44:23
WriteLine(5<4? Now()); //01.01.0001 0:00:00
WriteLine(Expr(5<4? Now())); //@If(5<4,Now(),)

Если условие не выполняется, то возвращается значение по умолчанию для типа true-выражения.

//А ещё можно так использовать:
bool b=true;
b ? WriteLine("TRUE!") : WriteLine("FALSE!"); //TRUE!
b ? WriteLine("OK!"); //OK!

Optional chaining

Оператор ?. заменяется на функцию IfNotNull, которая возвращает значение второго аргумента, если первый не равен null, а если равен, то возвращает null. Это оператор опциональной последовательности (Optional chaining operator). Если в цепочке объектов какой-то элемент равен null, то возвращается null.

Foo foo=new Foo("Hello");
WriteLine("Str: "+foo?.Str); //Str: Hello
foo=null;
WriteLine("Str: "+(foo?.Str==null)); //Str: True

WriteLine(Expr(foo?.Str)); //@IfNotNull((var °chainItem_3=foo),_(°chainItem_3.Str,°chainItem_3=null))

//Кстати, вместо ?. можно делать так:
WriteLine("Str: "+_(foo.Str)("error_value")); //Str: error_value
//_(foo.Str) - это функция возвращающая значение первого аргумента, а ("error_value") - какое значение вернуть в случае, если функция перед выдаст исключение

class Foo{
    public string Str;
    New(string s){
        Str=s;
    }
}

°chainItem_3 - это автоматически созданная переменная для хранения значения текущего звена, чтобы избежать ситуации, при которой значение проверенное в условии может быть изменено другим потоком во время работы функции IfNotNull до получения значения второго аргумента. К тому же, это кеширование значений позволяет не вычислять по несколько раз одни и те же сложные выражения. Имена таких служебных переменных начинаются со знака градуса, который нельзя использовать для пользовательских переменных.
Из примера видно, что вторым аргументом IfNotNull идёт: _(°chainItem_3 .Str,°chainItem_3=null). Функция _(аргумент_1, аргумент_2, аргумент_3, ...) возвращает значение первого аргумента и высчитывает остальные. Т.е. будет возвращено значение °chainItem_3.Str и выполнено °chainItem_3=null, чтобы обнулить временную переменную.

А вот, как для элементов массива использовать:

string[] arr={"foo", "bar", "baz"};
WriteLine(arr?[2]); //baz
arr=null;
WriteLine(arr?[2]==null); //True
//WriteLine(arr[2]); //выдаст исключение NullReferenceException

Есть оператор ?-> для Reflection-цепочек:

WriteLine("hello world"->Substring(6)?->ToUpper()); //WORLD

В выражениях для констант optional chaining не поддерживается, т.к. в них нельзя использовать переменные, и chainItem-переменная вызовет ошибку.

const string t="test";
const string s=t?.ToUpper(); //выдаст ошибку "Variable '°chainItem_1' not found."

Оператора ??= нет.

Sign In or Register to comment.