Tôi có thể đoán hợp lý những gì đang diễn ra ở đây, nhưng tất cả đều hơi phức tạp :) Nó liên quan đến trạng thái null và theo dõi null được mô tả trong thông số dự thảo . Về cơ bản, tại điểm mà chúng tôi muốn quay lại, trình biên dịch sẽ cảnh báo nếu trạng thái của biểu thức là "có thể null" thay vì "không null".
Câu trả lời này ở dạng tường thuật hơn là "đây là kết luận" ... Tôi hy vọng nó hữu ích hơn theo cách đó.
Tôi sẽ đơn giản hóa ví dụ một chút bằng cách loại bỏ các trường và xem xét một phương thức với một trong hai chữ ký sau:
public static string M(string? text)
public static string M(string text)
Trong các triển khai dưới đây, tôi đã cung cấp cho mỗi phương thức một số khác nhau để tôi có thể tham khảo các ví dụ cụ thể rõ ràng. Nó cũng cho phép tất cả các triển khai có mặt trong cùng một chương trình.
Trong mỗi trường hợp được mô tả dưới đây, chúng tôi sẽ làm nhiều việc khác nhau nhưng cuối cùng lại cố gắng quay lại text
- vì vậy đó là trạng thái text
không quan trọng.
Hoàn trả vô điều kiện
Đầu tiên, chúng ta hãy thử trả lại trực tiếp:
public static string M1(string? text) => text; // Warning
public static string M2(string text) => text; // No warning
Cho đến nay, rất đơn giản. Trạng thái nullable của tham số khi bắt đầu phương thức là "có thể null" nếu nó thuộc loại string?
và "không null" nếu thuộc loại string
.
Hoàn trả có điều kiện đơn giản
Bây giờ hãy kiểm tra null trong if
chính điều kiện câu lệnh. (Tôi sẽ sử dụng toán tử có điều kiện, mà tôi tin rằng sẽ có tác dụng tương tự, nhưng tôi muốn giữ nguyên câu hỏi.)
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
}
}
Tuyệt vời, vì vậy nó trông giống như trong một if
câu lệnh trong đó điều kiện tự kiểm tra tính vô hiệu, trạng thái của biến trong mỗi nhánh của if
câu lệnh có thể khác nhau: trong else
khối, trạng thái "không null" trong cả hai đoạn mã. Vì vậy, đặc biệt, trong M3, trạng thái thay đổi từ "có thể null" thành "không null".
Trả về có điều kiện với một biến cục bộ
Bây giờ chúng ta hãy cố gắng đưa điều kiện đó đến một biến cục bộ:
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
}
}
Cả M5 và M6 đều đưa ra cảnh báo. Vì vậy, không chỉ chúng tôi không nhận được hiệu ứng tích cực của thay đổi trạng thái từ "có thể null" thành "không null" trong M5 (như chúng tôi đã làm trong M3) ... chúng tôi còn nhận được hiệu ứng ngược lại ở M6, nơi nhà nước đi từ " không null "thành" có thể null ". Điều đó thực sự làm tôi ngạc nhiên.
Vì vậy, có vẻ như chúng ta đã học được rằng:
- Logic xung quanh "cách một biến cục bộ được tính toán" không được sử dụng để truyền bá thông tin trạng thái. Thêm về điều đó sau.
- Giới thiệu một so sánh null có thể cảnh báo trình biên dịch rằng tất cả những gì trước đây nó nghĩ là null có thể là null.
Hoàn trả vô điều kiện sau khi so sánh bị bỏ qua
Chúng ta hãy nhìn vào điểm thứ hai của những gạch đầu dòng đó, bằng cách đưa ra một so sánh trước khi trở lại vô điều kiện. (Vì vậy, chúng tôi hoàn toàn bỏ qua kết quả so sánh.):
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
}
Lưu ý cách M8 cảm thấy nó tương đương với M2 - cả hai đều có tham số không null mà chúng trả về vô điều kiện - nhưng việc đưa ra so sánh với null thay đổi trạng thái từ "không null" thành "có thể null". Chúng ta có thể có thêm bằng chứng về điều này bằng cách cố gắng hủy đăng ký text
trước điều kiện:
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
}
Lưu ý cách return
bây giờ câu lệnh không có cảnh báo: trạng thái sau khi thực thi text.Length
là "không null" (vì nếu chúng ta thực hiện thành công biểu thức đó, nó không thể là null). Vì vậy, text
tham số bắt đầu là "không null" do loại của nó, trở thành "có thể null" do so sánh null, sau đó trở thành "không null" sau đó text2.Length
.
Những so sánh ảnh hưởng đến nhà nước?
Vì vậy, đó là một so sánh của text is null
... những so sánh tương tự có ảnh hưởng gì? Dưới đây là bốn phương thức nữa, tất cả đều bắt đầu bằng tham số chuỗi không null:
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
}
Vì vậy, mặc dù x is object
bây giờ là một giải pháp thay thế được đề xuất x != null
, chúng không có tác dụng tương tự: chỉ so sánh với null (với bất kỳ is
, ==
hoặc !=
) thay đổi trạng thái từ "không null" thành "có thể null".
Tại sao cẩu nâng điều kiện có ảnh hưởng?
Quay trở lại điểm đầu tiên của chúng tôi trước đó, tại sao M5 và M6 không tính đến điều kiện dẫn đến biến cục bộ? Điều này không làm tôi ngạc nhiên nhiều như nó làm người khác ngạc nhiên. Xây dựng loại logic đó vào trình biên dịch và đặc tả là rất nhiều công việc và cho lợi ích tương đối ít. Đây là một ví dụ khác không liên quan gì đến tính vô hiệu trong đó nội tuyến có gì đó có ảnh hưởng:
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
}
Mặc dù chúng tôi biết rằng điều đó alwaysTrue
sẽ luôn đúng, nhưng nó không thỏa mãn các yêu cầu trong đặc tả làm cho mã sau if
câu lệnh không thể truy cập được, đó là điều chúng tôi cần.
Đây là một ví dụ khác, xung quanh nhiệm vụ xác định:
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);
}
Mặc dù chúng tôi biết rằng mã sẽ nhập chính xác một trong các if
cơ quan tuyên bố đó, nhưng không có gì trong thông số kỹ thuật để làm việc đó. Các công cụ phân tích tĩnh có thể có thể làm như vậy, nhưng cố gắng đưa nó vào đặc tả ngôn ngữ sẽ là một ý tưởng tồi, IMO - thật tốt khi các công cụ phân tích tĩnh có tất cả các loại heuristic có thể phát triển theo thời gian, nhưng không quá nhiều cho một đặc điểm kỹ thuật ngôn ngữ.