Brak wartości i specjalne wartości puste
Odróżniamy od siebie trzy różne rodzaje wartości pól:
- zwykłe wartości niepuste: liczby, napisy, np. 15, 123.00, „ala miała chomika”, (2008/07/01 12:13:45)
- braki wartości (null) – sytuacje dopuszczalne dla wszystkich typów pól, co ustawiamy przełącznikiem wymagalności pola,
- specjalne wartości puste nie będące null – pusty napis, pusty ciąg bajtów (dane binarne), pusta lista, specjalny klucz Guid {00000000-0000-0000-0000-000000000000}
Do odróżniania tych przypadków od siebie służą 3 funkcje: IsNullOrEmpty, IsNullNotEmpty, IsEmptyNotNull, które zawsze zwracają konkretny wynik logiczny true/false, nigdy nie zwracają null. Ostatnia z tych funkcji jest dostępna jedynie dla niektórych typów danych, dla których jest zdefiniowana wartość pusta: Napis, Guid, Binarny, Lista.

Poniższa tabela ilustruje wartości tych funkcji dla wszystkich przypadków danych wejściowych.
| Funkcja/rodzaj wartości | Normalna wartość niepusta, np. „Ala” | Brak wartości – null | Wartość pusta, np. „” |
|---|---|---|---|
IsNullOrEmpty (a) | False | True | True |
IsNullNotEmpty (a) | False | True | False |
IsEmptyNotNull (a) | False | False lub BŁĄD KOMPILACJI dla stałej „null” i typów niedefiniujących wartości pustej | True |
IsNull() i IsNotNull
W poprzednich wersjach platformy funkcjonowały powszechnie funkcje IsNull() i IsNotNull, które działały tak, jak obecne IsNullOrEmpty, jednak z uwagi na nieporozumienia dotyczące wykonywanych sprawdzeń, zostały oznaczone jako „obsolete”. Występują w podpowiadaniu jako przekreślone, a ich użycie jest zgłaszane jako komunikat w trybie projektowania wyrażenia. Nie planujemy wycofania ich z użycia z uwagi na konieczność wykonania migracji.
Brak wartości jest interpretowany na sposób SQL-owy, czyli oznacza wartość nieznaną. W szczególności operatory porównania zachowują się tak, jak w SQL. Każde wywołanie funkcji (lub operatora) nieprzygotowanego na wartość pustą jest dla wejściowej pustej wartości automatycznie zamieniane na pusty wynik. W arytmetyce prowadzi to do poniższych wyników. Napis a_null oznacza tu brak wartości zmiennej a_null jakiegoś konkretnego typu, dla odróżnienia od stałego literału null, który obsługiwany jest miejscami w specjalny sposób, tzn. porównania.
| Obliczenie | Wynik i komentarz |
|---|---|
2 + a_null | a_null |
Substring(a_null, 0, 5) | a_null |
2 == a_null | a_null |
2 > a_null | a_null |
2 >= a_null | a_null |
a_null == a_null | zależy od typu – patrz punkt poniżej dot. napisów |
a_null = a_null | a_null - operatory > i < nie są określone dla napisów |
a_null >= a_null | a_null - j.w. |
Opisywana arytmetyka może w pierwszym momencie być zaskakująca, dlatego środowisko projektowe umieszcza informację (podkreślenie + tooltip) przy każdym użyciu wymienionych konstrukcji dla wartości niewymagalnych, gdzie wynikiem może być null.
Porównania wartości niewymagalnych w języku C#
Przedstawione wyniki porównań > i < przynajmniej jednego braku wartości (null) w języku C# będą zawsze wartością logiczną False, nawet dla a_null>= a_null, chociaż a_null == a_null da tam wartość logiczną True. Jak można zauważyć, nie jest tu niestety spełniona tożsamość znana ze szkoły:
a>=b WTW a>b | a==b
To samo zachodzi dla operatora <=
Typ napisowy
Obsługa wartości typu napisowego stanowi wyjątek od prostego, „biznesowo zorientowanego” modelu przetwarzania wzorowanego na SQL i przypomina ten znany z MS SQL Servera, tzn. da się odróżnić pusty napis od braku wartości (null). Jedynie dla napisów subtelność tę wyłapują operatory == oraz != Jest to trudna do zmiany zaszłość z wcześniejszych wersji. Dla innych typów należy posługiwać się opisanymi wcześniej funkcjami odróżniającymi trzy rodzaje wartości.
Poniższa tabela prezentuje wyniki dla rożnych sprawdzeń wartości napisowej A, w szczególności ostatnia kolumna ilustruje opisywany tu wyjątek.
| Wartość A | Length(A) | A is_empty | IsNull(A) | A==null |
|---|---|---|---|---|
| null Brak wartości | 0 Zawsze wymagalna liczba, nie null! | True | True | True .. a nie „null” |
| „” Pusty napis | 0 | True | True | False .. a nie „null” |
| „ala ma kota” | 11 | False | False | False |
Jak można zauważyć, funkcja Length(a) zawsze zwraca konkretną liczbę, nigdy brak wartości (null). Pod tym względem, wszystkie funkcje dotyczące napisów zachowują się w podobny sposób.
Logika Bool-a dla braku wartości
W Systemie uwzględniającym brak wartości powstaje pytanie o zachowanie i wyniki zwracane przez operatory logiczne. Pośród języków programowania można znaleźć różne rozwiązania tego zagadnienia.
Przyjęliśmy następujące, możliwie intuicyjne rozwiązania, próbujące zwrócić możliwie najwięcej informacji logicznej dla niepełnych danych wejściowych, przy zachowaniu rozwiązywania wyrażeń od lewej do prawej.
W poniższych przykładach oznaczenia interpretujemy następująco:
a_truema wartość True,a_nullnie ma wartości (null),f_null()– funkcja (lub wyrażenie) zwracająca logiczny brak wartości.
| Wyrażenie | Wynik |
|---|---|
a_true && a_null | null |
| `a_true | |
a_false && a_null | False |
| `a_false | |
a_null && a_true | null |
| `a_null | |
a_null && a_false | False |
| `a_null |
Wartości logiczne, będące wynikiem opisywanych operatorów są wymagalne tylko, jeśli wszystkie argumenty są wymagalne.
Podczas wyliczania wartości operatorów logicznych, podobnie jak trynarnego i omijania braku wartości (?? i ???) elementy wyrażeń są rozwiązywane tylko, jeśli ich wartość wniesie coś do wyniku. Jeśli w powyższych przykładach zastąpimy a_null przez f_null() możemy zadać pytanie, czy owa funkcja będzie wywołana, czy nie:
| Wyrażenie | Wynik | Czy wywołanie f_null()? |
|---|---|---|
a_true && f_null | null | |
| `a_true | f_null ` | |
a_false && f_null | False | Nie |
| `a_false | f_null` | |
f_null && a_true | null | |
| `f_null | a_true` | |
f_null && a_false | False | Tak – rozwiązywanie od lewej do prawej |
| `f_null | a_false` |
Powstaje też pytanie, co się stanie, gdy lewa strona operatora będzie null:
| Wyrażenie | Wynik | Czy wywołanie f_null()? |
|---|---|---|
a_null && f_null() | null | Nie |
| `a_null | f_null()` |
Logika wartości pustych w języku C# jest zabroniona! Kompilator zgłasza błąd o braku stosownych operatorów dla Nullable<bool>