Saya dapat membuat perkiraan yang masuk akal tentang apa yang terjadi di sini, tapi itu semua sedikit rumit :) Ini melibatkan keadaan nol dan pelacakan nol yang dijelaskan dalam spesifikasi draf . Pada dasarnya, pada titik di mana kita ingin kembali, kompiler akan memperingatkan jika keadaan ekspresi "mungkin nol" bukannya "bukan nol".
Jawaban ini agak dalam bentuk naratif daripada hanya "inilah kesimpulannya" ... Saya harap itu lebih berguna seperti itu.
Saya akan menyederhanakan contoh sedikit dengan menyingkirkan bidang, dan mempertimbangkan metode dengan salah satu dari dua tanda tangan ini:
public static string M(string? text)
public static string M(string text)
Dalam implementasi di bawah ini saya telah memberikan masing-masing metode nomor yang berbeda sehingga saya dapat merujuk pada contoh spesifik dengan jelas. Ini juga memungkinkan semua implementasi untuk hadir dalam program yang sama.
Dalam setiap kasus yang dijelaskan di bawah ini, kami akan melakukan berbagai hal tetapi akhirnya berusaha untuk kembali text
- jadi keadaan nol text
yang penting.
Pengembalian tanpa syarat
Pertama, mari kita coba mengembalikannya secara langsung:
public static string M1(string? text) => text; // Warning
public static string M2(string text) => text; // No warning
Sejauh ini, sangat sederhana. Status parameter yang dapat dibatalkan pada awal metode adalah "mungkin nol" jika bertipe string?
dan "tidak nol" jika bertipe string
.
Pengembalian bersyarat sederhana
Sekarang mari kita periksa nol dalam if
kondisi pernyataan itu sendiri. (Saya akan menggunakan operator kondisional, yang saya percaya akan memiliki efek yang sama, tetapi saya ingin tetap lebih benar dengan pertanyaan itu.)
public static string M3(string? text)
{
if (text is null)
{
return "";
}
else
{
return text; // No warning
}
}
public static string M4(string text)
{
if (text is null)
{
return "";
}
else
{
return text; // No warning
}
}
Hebat, jadi seperti di dalam if
pernyataan di mana kondisi itu sendiri memeriksa nullity, keadaan variabel dalam setiap cabang if
pernyataan bisa berbeda: dalam else
blok, negara adalah "tidak nol" di kedua potongan kode. Jadi khususnya, dalam M3 keadaan berubah dari "mungkin nol" menjadi "tidak nol".
Pengembalian bersyarat dengan variabel lokal
Sekarang mari kita coba menaikkan kondisi itu ke variabel lokal:
public static string M5(string? text)
{
bool isNull = text is null;
if (isNull)
{
return "";
}
else
{
return text; // Warning
}
}
public static string M6(string text)
{
bool isNull = text is null;
if (isNull)
{
return "";
}
else
{
return text; // Warning
}
}
Baik M5 dan M6 mengeluarkan peringatan. Jadi tidak hanya kita tidak mendapatkan efek positif dari perubahan status dari "mungkin nol" menjadi "tidak nol" di M5 (seperti yang kita lakukan dalam M3) ... kita mendapatkan efek sebaliknya di M6, di mana negara beralih dari " bukan null "menjadi" mungkin null ". Itu benar-benar mengejutkan saya.
Jadi sepertinya kita telah belajar bahwa:
- Logika seputar "bagaimana variabel lokal dihitung" tidak digunakan untuk menyebarkan informasi keadaan. Lebih lanjut tentang itu nanti.
- Memperkenalkan perbandingan nol dapat memperingatkan kompiler bahwa sesuatu yang sebelumnya dianggap bukan nol mungkin nol.
Pengembalian tanpa syarat setelah perbandingan yang diabaikan
Mari kita lihat poin poin kedua, dengan memperkenalkan perbandingan sebelum pengembalian tanpa syarat. (Jadi kami sepenuhnya mengabaikan hasil perbandingan.):
public static string M7(string? text)
{
bool ignored = text is null;
return text; // Warning
}
public static string M8(string text)
{
bool ignored = text is null;
return text; // Warning
}
Perhatikan bagaimana M8 terasa seperti itu harus setara dengan M2 - keduanya memiliki parameter tidak-nol yang mereka kembalikan tanpa syarat - tetapi pengenalan perbandingan dengan nol mengubah status dari "tidak nol" menjadi "mungkin nol". Kita bisa mendapatkan bukti lebih lanjut tentang ini dengan mencoba melakukan dereferensi text
sebelum kondisinya:
public static string M9(string text)
{
int length1 = text.Length; // No warning
bool ignored = text is null;
int length2 = text.Length; // Warning
return text; // No warning
}
Perhatikan bagaimana return
pernyataan itu tidak memiliki peringatan sekarang: negara setelah mengeksekusi text.Length
adalah "tidak nol" (karena jika kita berhasil mengeksekusi ekspresi itu, itu tidak bisa menjadi nol). Jadi text
parameter dimulai sebagai "bukan nol" karena jenisnya, menjadi "mungkin nol" karena perbandingan nol, kemudian menjadi "bukan nol" lagi setelahnya text2.Length
.
Apa perbandingan yang memengaruhi status?
Jadi itu perbandingan text is null
... apa pengaruh perbandingan serupa? Berikut adalah empat metode lagi, semua dimulai dengan parameter string yang tidak dapat dibatalkan:
public static string M10(string text)
{
bool ignored = text == null;
return text; // Warning
}
public static string M11(string text)
{
bool ignored = text is object;
return text; // No warning
}
public static string M12(string text)
{
bool ignored = text is { };
return text; // No warning
}
public static string M13(string text)
{
bool ignored = text != null;
return text; // Warning
}
Jadi meskipun x is object
sekarang merupakan alternatif yang disarankan untuk x != null
, mereka tidak memiliki efek yang sama: hanya perbandingan dengan nol (dengan salah satu dari is
, ==
atau !=
) mengubah negara dari "tidak nol" menjadi "mungkin nol".
Mengapa mengangkat kondisi itu berpengaruh?
Kembali ke poin pertama kita sebelumnya, mengapa M5 dan M6 tidak memperhitungkan kondisi yang menyebabkan variabel lokal? Ini tidak mengejutkan saya seperti halnya mengejutkan orang lain. Membangun semacam logika ke kompiler dan spesifikasi banyak pekerjaan, dan untuk manfaat yang relatif sedikit. Berikut ini contoh lain yang tidak ada hubungannya dengan nullability di mana inlining memiliki efek:
public static int X1()
{
if (true)
{
return 1;
}
}
public static int X2()
{
bool alwaysTrue = true;
if (alwaysTrue)
{
return 1;
}
// Error: not all code paths return a value
}
Meskipun kita tahu itu alwaysTrue
akan selalu benar, itu tidak memenuhi persyaratan dalam spesifikasi yang membuat kode setelah if
pernyataan tidak terjangkau, yang merupakan apa yang kita butuhkan.
Berikut contoh lain, seputar penugasan yang pasti:
public static void X3()
{
string x;
bool condition = DateTime.UtcNow.Year == 2020;
if (condition)
{
x = "It's 2020.";
}
if (!condition)
{
x = "It's not 2020.";
}
// Error: x is not definitely assigned
Console.WriteLine(x);
}
Meskipun kita tahu bahwa kode akan memasukkan tepat salah satu dari if
badan pernyataan itu, tidak ada dalam spesifikasi untuk menyelesaikannya. Alat analisis statis mungkin dapat melakukannya, tetapi mencoba memasukkannya ke dalam spesifikasi bahasa akan menjadi ide yang buruk, IMO - tidak apa-apa jika alat analisis statis memiliki semua jenis heuristik yang dapat berkembang seiring waktu, tetapi tidak begitu banyak untuk spesifikasi bahasa.