1. Giriş
Təsəvvür elə ki, proqram kodu — bir tamaşadır, dəyişənlər isə aktyorlardır. Hər dəfə dəyişən yeni səhnəyə (kod bloku) çıxanda, o ya "canlı" olur (dəyəri var), ya da "ruh" (null). Amma problem ondadır ki, tamaşaçı — kompilyator — tamaşanı canlı yox, ssenariyə baxaraq izləyir. O görür ki, aktyorlara rollar harada verilir (dəyər təyin olunur), harada yoxa çıxırlar (null olurlar). Amma reallığa (proqramın icrasına) baxa bilmir — yalnız ssenariyə əsasən təxmin edə bilər!
Data flow-un statik analizi — bu prosesdir ki, kompilyator proqram işə düşməmişdən əvvəl kodu yoxlayır ki, birdən kimsə "NullReferenceException ilə yıxılma səhnəsi" oynamaq istədi.
Prosesin mahiyyəti
C# 8.0-dan və Nullable Reference Types (NRT) rejimi gələndən kompilyator yoxlayır ki, dəyişən hansısa icra yolunda null ola bilər ya yox. Əgər ola bilərsə — dərhal xəbər verir. Yəni sənə "birdən aktyor ruh oldu, adını istifadə etmə" deməyə imkan vermir.
2. Kompilyatorun xəbərdarlıqlarını necə oxumaq lazımdır
Əsas xəbərdarlıq kateqoriyaları
NRT rejimi aktiv olanda, kompilyator xüsusilə referans dəyişənləri (string, User, massivlər, obyektlər) diqqətlə izləyir. Ən çox rast gəlinən xəbərdarlıq tipləri bunlardır:
- Possible null assignment — sən null ola biləcək dəyəri, null qəbul etməyən dəyişənə təyin etməyə çalışırsan.
- Dereference of a possibly null reference — obyektin üzvünə müraciət edirsən, amma kompilyator əmin deyil ki, o null deyil.
- Possible null return — metod null qaytara bilər, halbuki imzasına görə dəyər qaytarmalıdır.
- Nullable object must have a value — sən nullable tipdə .Value istifadə edirsən, amma yoxlamadan.
- Unassigned non-nullable property — sən null qəbul etməyən sahəyə/sahibə dəyər verməmisən.
Adətən IDE-də (məsələn, Rider və ya Visual Studio) belə xəbərdarlıqlar dalğalı xəttlə vurğulanır, siçanla üstünə gələndə izah çıxır.
Tipik xəbərdarlıq nümunəsi
#nullable enable
string? possibleNull = GetString();
// Ay-ay, kompilyator deyinir: "Dereference of a possibly null reference"
int length = possibleNull.Length; // Xəbərdarlıq!
Kompilyator əmin deyil ki, possibleNull null deyil, yəni .Length çağırışı istisna ata bilər.
Əgər metod belədirsə:
string? GetString() { ... }
Və sən belə yazırsan:
string nonNullable = GetString();
Onda xəbərdarlıq alırsan: null non-nullable dəyişənə təhlükəli ötürülə bilər.
3. Kompilyator kodunu analiz edir
Nümunə 1: düz təyinat
string? name = null;
Console.WriteLine(name.Length); // Xəbərdarlıq: null-a müraciət ola bilər
Burada hər şey aydındır: name null-dır, deməli property-ə müraciət təhlükəlidir.
Nümunə 2: null yoxlaması
string? name = GetUserName();
if (name != null)
{
Console.WriteLine(name.Length); // Hər şey ok!
}
else
{
Console.WriteLine("Ad göstərilməyib!");
}
Kompilyator başa düşür ki, if (name != null) blokunda dəyişən təhlükəsiz sayılır. Bu da flow analysis — şərtlərə görə dəyərlərin axınının analizidir.
4. Kompilyator "metodun içinə" baxa bilmir
O, yalnız imzaya baxır — əgər yazılıb ki, metod string? qaytarır, o inanır ki, null ola bilər, hətta içəridə həmişə string qaytarılsa belə.
Nümunə 3: metoddan dəyər qaytarmaq
string? GetMaybeName(bool useName)
{
if (useName)
return "Code Jedi";
else
return null;
}
void PrintLength()
{
string? name = GetMaybeName(false);
Console.WriteLine(name.Length); // Xəbərdarlıq!
}
Kompilyator görür ki, GetMaybeName ola bilər null qaytarsın, ona görə də diqqətli olmağını istəyir.
5. Yayğın ssenarilər və xəbərdarlıqlar
Nullable dəyişəni non-nullable dəyişənə təyin etmək
string? maybeUser = GetUser();
string alwaysUser = maybeUser; // warning: possible null assignment
Xəbərdarlığı aradan qaldırmaq üçün:
string alwaysUser = maybeUser ?? "Qonaq";
İndi həmişə string olacaq — ya metoddan, ya da "Qonaq".
Nullable value-type üçün .HasValue yoxlamasını unutmaq
int? age = GetAge();
int realAge = age.Value; // warning: null-a müraciət ola bilər
Ya belə et:
if (age.HasValue)
Console.WriteLine(age.Value);
Ya da:
int realAge = age ?? -1;
İlkinləşdirilməmiş avtomatik property-lər
class User
{
public string Name { get; set; } // warning: ilkinləşdirilməyib!
}
Həllər:
public string Name { get; set; } = "Adsız";
və ya
public string? Name { get; set; }
6. Flow analizinin çətin halları
Döngülərdə analiz
string? text = null;
while (text == null)
{
text = Console.ReadLine();
}
Console.WriteLine(text.Length); // hər şey ok: kompilyator başa düşdü!
Müxtəlif təyinat yolları
string? name;
if (Random.Shared.Next() % 2 == 0)
name = "Alice";
else
name = null;
Console.WriteLine(name.Length); // warning: possible null dereference
Kompilyator bütün yolları analiz edir. Əgər heç olmasa birində null varsa — xəbərdarlıq verir.
7. Kompilyatoru necə "sakitləşdirmək" olar (və nə vaxt bunu etməmək lazımdır)
Bəzən sən əminsən ki, dəyər null ola bilməz, amma kompilyator inanmır:
string? value = GetSomething();
if (value == null)
throw new Exception("Dəyər gözlənilirdi!");
Console.WriteLine(value.Length); // Xəbərdarlıq yox olur
Və ya sən ! operatorundan istifadə edirsən:
string? value = GetSomething();
Console.WriteLine(value!.Length); // Xəbərdarlıq yoxdur, amma təhlükəlidir
Buna null-forgiving operator deyilir. O heç nə yoxlamır — sadəcə kompilyatora deyir: "sakit ol, hər şey nəzarətdədir". Amma səhv etsən — runtime-da istisna alacaqsan.
GO TO FULL VERSION