Tại sao lại có sự khác biệt trong việc kiểm tra null so với giá trị trong VB.NET và C #?


110

Trong VB.NET điều này xảy ra:

Dim x As System.Nullable(Of Decimal) = Nothing
Dim y As System.Nullable(Of Decimal) = Nothing

y = 5
If x <> y Then
    Console.WriteLine("true")
Else
    Console.WriteLine("false") '' <-- I got this. Why?
End If

Nhưng trong C # điều này xảy ra:

decimal? x = default(decimal?);
decimal? y = default(decimal?);

y = 5;
if (x != y)
{
    Debug.WriteLine("true"); // <-- I got this -- I'm with you, C# :)
}
else
{
    Debug.WriteLine("false");
}

Tại sao lại có một sự khác biệt?


22
thật kinh khủng.
Mikeb

8
Tôi tin rằng default(decimal?)trả về 0, không phải null.
Ryan Frame,

7
@RyanFrame KHÔNG. Vì nó là loại nullable , nó sẽ trảnull
Soner Gonul

4
Oh yeah ... right ... trong VB Ifđiều kiện không yêu cầu đánh giá như là một boolean ... uuuugh EDIT: Vì vậy Nothing <> Anything = Nothing, kết quả trong việc Iflấy tuyến đường phủ định / khác.
Chris Sinclair

13
@JMK: Null, Nothing và Empty thực sự khác nhau một cách tinh tế. Nếu tất cả chúng đều giống nhau thì bạn sẽ không cần ba trong số chúng.
Eric Lippert

Câu trả lời:


88

VB.NET và C # .NET là các ngôn ngữ khác nhau, được xây dựng bởi các nhóm khác nhau, những người đã đưa ra các giả định khác nhau về cách sử dụng; trong trường hợp này là ngữ nghĩa của phép so sánh NULL.

Sở thích cá nhân của tôi là dành cho ngữ nghĩa VB.NET, về bản chất, nó mang lại cho NULL ngữ nghĩa "Tôi chưa biết". Sau đó, so sánh của 5 với "Tôi chưa biết". tự nhiên là "Tôi chưa biết"; tức là NULL. Điều này có lợi thế bổ sung là sao chép hành vi của NULL trong (hầu hết nếu không phải tất cả) cơ sở dữ liệu SQL. Đây cũng là cách diễn giải chuẩn hơn (hơn C #) về logic ba giá trị, như được giải thích ở đây .

Nhóm C # đã đưa ra các giả định khác nhau về ý nghĩa của NULL, dẫn đến sự khác biệt về hành vi mà bạn thể hiện. Eric Lippert đã viết một blog về ý nghĩa của NULL trong C # . Per Eric Lippert: "Tôi cũng đã viết về ngữ nghĩa của null trong VB / VBScript và JScript ở đâyở đây ".

Trong bất kỳ môi trường nào có thể có giá trị NULL, không thể dựa vào Luật Vùng trung bình bị loại trừ (tức là A hoặc ~ A đúng về mặt tautology) không còn có thể được dựa vào.

Cập nhật:

A bool(trái ngược với a bool?) chỉ có thể nhận các giá trị TRUE và FALSE. Tuy nhiên, một ngôn ngữ thực thi NULL phải quyết định cách NULL truyền thông qua các biểu thức. Trong VB, các biểu thức 5=null5<>nullBOTH trả về false. Trong C #, các biểu thức so sánh 5==null5!=nullchỉ thứ hai đầu tiên [cập nhật 2014/03/02 - PG] trả về FALSE. Tuy nhiên, trong BẤT KỲ môi trường nào hỗ trợ null, lập trình viên phải biết các bảng sự thật và cách truyền null được sử dụng bởi ngôn ngữ đó.

Cập nhật

Các bài viết trên blog của Eric Lippert (được đề cập trong phần bình luận của anh ấy bên dưới) về ngữ nghĩa hiện tại:


4
Cảm ơn các liên kết. Tôi cũng đã viết về ngữ nghĩa của null trong VB / VBScript và JScript tại đây: blog.msdn.com/b/ericlippert/archive/2003/09/30/53120.aspx và tại đây: blogs.msdn.com/b/ericlippert/ lưu trữ / 2003/10/01 / 53128.aspx
Eric Lippert

27
Và FYI quyết định làm cho C # không tương thích với VB theo cách này là một quyết định gây tranh cãi. Tôi không thuộc nhóm thiết kế ngôn ngữ vào thời điểm đó nhưng lượng tranh luận đi đến quyết định này rất đáng kể.
Eric Lippert

2
@ BlueRaja-DannyPflughoeft Trong C # boolkhông thể có 3 giá trị, chỉ có hai. Nó bool?có thể có ba giá trị. operator ==operator !=cả hai đều trả về bool, không bool?, bất kể loại toán hạng. Ngoài ra, một ifcâu lệnh chỉ có thể chấp nhận a bool, không phải a bool?.
Servy

1
Trong C # các biểu thức 5=null5<>nullkhông hợp lệ. Và trong số 5 == null5 != null, bạn có chắc đó là lần thứ hai quay lại false?
Ben Voigt

1
@BenVoigt: Cảm ơn bạn. Tất cả những phiếu ủng hộ đó và bạn là người đầu tiên phát hiện lỗi đánh máy đó. ;-)
Pieter Geerkens

37

Bởi vì x <> ytrả lại Nothingthay vì true. Nó chỉ đơn giản là không được định nghĩa vì xkhông được xác định. (tương tự như SQL null).

Lưu ý: VB.NET Nothing<> C # null.

Bạn cũng phải so sánh giá trị của một Nullable(Of Decimal)chỉ nếu nó có một giá trị.

Vì vậy, VB.NET ở trên so sánh tương tự như thế này (trông ít sai hơn):

If x.HasValue AndAlso y.HasValue AndAlso x <> y Then
    Console.WriteLine("true")
Else
    Console.WriteLine("false")  
End If

VB.NET tả ngôn ngữ :

7.1.1 Các kiểu giá trị có thể nullable ... Một kiểu giá trị nullable có thể chứa các giá trị giống như phiên bản không thể null của kiểu cũng như giá trị null. Do đó, đối với kiểu giá trị nullable, việc gán Không có gì cho một biến của kiểu sẽ đặt giá trị của biến thành giá trị null, không phải giá trị 0 của kiểu giá trị.

Ví dụ:

Dim x As Integer = Nothing
Dim y As Integer? = Nothing

Console.WriteLine(x) ' Prints zero '
Console.WriteLine(y) ' Prints nothing (because the value of y is the null value) '

16
"VB.NET Nothing <> C # null" có trả về true cho C # và false cho VB.Net không? Đùa thôi :-p
ken2k Ngày

17

Nhìn vào CIL đã (tôi đã chuyển đổi cả hai thành C #):

C #:

private static void Main(string[] args)
{
    decimal? x = null;
    decimal? y = null;
    y = 5M;
    decimal? CS$0$0000 = x;
    decimal? CS$0$0001 = y;
    if ((CS$0$0000.GetValueOrDefault() != CS$0$0001.GetValueOrDefault()) ||
        (CS$0$0000.HasValue != CS$0$0001.HasValue))
    {
        Console.WriteLine("true");
    }
    else
    {
        Console.WriteLine("false");
    }
}

Ngôn ngữ lập trình:

[STAThread]
public static void Main()
{
    decimal? x = null;
    decimal? y = null;
    y = 5M;
    bool? VB$LW$t_struct$S3 = new bool?(decimal.Compare(x.GetValueOrDefault(), y.GetValueOrDefault()) != 0);
    bool? VB$LW$t_struct$S1 = (x.HasValue & y.HasValue) ? VB$LW$t_struct$S3 : null;
    if (VB$LW$t_struct$S1.GetValueOrDefault())
    {
        Console.WriteLine("true");
    }
    else
    {
        Console.WriteLine("false");
    }
}

Bạn sẽ thấy rằng phép so sánh trong Visual Basic trả về Nullable <bool> (không phải bool, false hoặc true!). Và không xác định được chuyển đổi thành bool là sai.

Nothingso với bất cứ điều gì luôn luôn Nothing, không sai trong Visual Basic (nó giống như trong SQL).


Tại sao trả lời câu hỏi bằng cách thử và sai? Nên có thể làm điều đó từ các đặc tả ngôn ngữ.
David Heffernan

3
@DavidHeffernan, bởi vì điều này cho thấy sự khác biệt về ngôn ngữ khá rõ ràng.
nothrow

2
@Yossarian Bạn nghĩ rằng thông số ngôn ngữ không rõ ràng về vấn đề này. Tôi không đồng ý. IL là một chi tiết thực hiện có thể thay đổi; các thông số kỹ thuật không.
Servy

2
@DavidHeffernan: Tôi thích thái độ của bạn và khuyến khích bạn cố gắng. Đôi khi, đặc tả ngôn ngữ VB có thể khó phân tích cú pháp. Lucian đã cải thiện nó trong một số năm nay nhưng vẫn có thể khá khó khăn để tìm ra ý nghĩa chính xác của các loại trường hợp góc này. Tôi khuyên bạn nên lấy một bản sao của thông số kỹ thuật, thực hiện một số nghiên cứu và báo cáo những phát hiện của bạn.
Eric Lippert vào

2
@Yossarian Kết quả thực thi mã IL mà bạn đã cung cấp không thể thay đổi, nhưng mã C # / VB được cung cấp sẽ được biên dịch thành mã IL mà bạn đã hiển thị thể thay đổi (miễn là hành vi của IL đó là cũng phù hợp với định nghĩa của thông số ngôn ngữ).
Servy

6

Vấn đề được quan sát ở đây là một trường hợp đặc biệt của một vấn đề tổng quát hơn, đó là số lượng các định nghĩa khác nhau về bình đẳng có thể hữu ích trong ít nhất một số trường hợp vượt quá số lượng các phương tiện phổ biến hiện có để diễn đạt chúng. Trong một số trường hợp, vấn đề này trở nên tồi tệ hơn bởi một niềm tin đáng tiếc rằng thật khó hiểu khi có các phương tiện kiểm tra sự bình đẳng khác nhau mang lại kết quả khác nhau và có thể tránh được sự nhầm lẫn đó bằng cách để các dạng bình đẳng khác nhau mang lại kết quả giống nhau bất cứ khi nào có thể.

Trên thực tế, nguyên nhân cơ bản của sự nhầm lẫn là một niềm tin sai lầm rằng các hình thức kiểm tra bình đẳng và bất bình đẳng khác nhau nên được mong đợi mang lại cùng một kết quả, mặc dù thực tế là các ngữ nghĩa khác nhau sẽ hữu ích trong các trường hợp khác nhau. Ví dụ, từ quan điểm số học, sẽ rất hữu ích khi có thể có Decimalsố chỉ khác nhau về số lượng các số 0 ở cuối so sánh là bằng nhau. Tương tự như vậy đối với doublecác giá trị như số 0 dương và số 0 âm. Mặt khác, từ quan điểm bộ nhớ đệm hoặc interning, ngữ nghĩa như vậy có thể gây chết người. Giả sử, ví dụ, một người có một Dictionary<Decimal, String>như vậy myDict[someDecimal]phải bằng someDecimal.ToString(). Một đối tượng như vậy sẽ có vẻ hợp lý nếu một người có nhiềuDecimalgiá trị mà người ta muốn chuyển đổi thành chuỗi và dự kiến ​​sẽ có nhiều bản sao. Thật không may, nếu sử dụng bộ nhớ đệm như vậy để chuyển đổi 12,3 m và 12,40 m, tiếp theo là 12,30 m và 12,4 m, các giá trị sau sẽ mang lại "12,3" và "12,40" thay vì "12,30" và "12,4".

Quay trở lại vấn đề hiện tại, có nhiều hơn một cách hợp lý để so sánh các đối tượng vô hiệu cho bằng nhau. C # có quan điểm mà ==toán tử của nó phải phản ánh hành vi của nó Equals.VB.NET có quan điểm rằng hành vi của nó phải phản ánh hành vi của một số ngôn ngữ khác, vì bất kỳ ai muốn Equalshành vi đều có thể sử dụng Equals. Theo một nghĩa nào đó, giải pháp phù hợp sẽ là có một cấu trúc "nếu" ba chiều và yêu cầu rằng nếu biểu thức điều kiện trả về kết quả ba giá trị, thì mã phải chỉ định điều gì sẽ xảy ra trongnull trường hợp đó. Vì đó không phải là một lựa chọn với các ngôn ngữ như chúng vốn có, nên giải pháp thay thế tốt nhất tiếp theo là chỉ cần tìm hiểu cách các ngôn ngữ khác nhau hoạt động và nhận ra rằng chúng không giống nhau.

Ngẫu nhiên, toán tử "Is" của Visual Basic, thiếu C, có thể được sử dụng để kiểm tra xem một đối tượng null có thực sự là null hay không. Trong khi người ta có thể đặt câu hỏi một cách hợp lý liệu một ifbài kiểm tra có nên chấp nhận a hay không Boolean?, việc các toán tử so sánh bình thường trả về Boolean?thay vì Booleankhi được gọi trên các kiểu nullable là một tính năng hữu ích. Ngẫu nhiên, trong VB.NET, nếu một người cố gắng sử dụng toán tử bình đẳng hơn là Is, người ta sẽ nhận được cảnh báo rằng kết quả của phép so sánh sẽ luôn là Nothingvà người ta nên sử dụng Isnếu muốn kiểm tra xem có điều gì đó là rỗng hay không.


Kiểm tra xem một lớp có rỗng trong C # hay không được thực hiện bởi == null. Và việc kiểm tra xem kiểu giá trị nullable có giá trị hay không được thực hiện bởi .hasValue. Có công dụng gì cho một Is Nothingnhà điều hành? C # không có isnhưng nó kiểm tra khả năng tương thích kiểu. Vì vậy, tôi thực sự không chắc đoạn cuối của bạn đang muốn nói gì.
ErikE

@ErikE: Cả vb.net và C # đều cho phép kiểm tra giá trị kiểu nullable bằng cách sử dụng so sánh với null, mặc dù cả hai ngôn ngữ đều coi đó là đường cú pháp để HasValuekiểm tra, ít nhất là trong trường hợp đã biết kiểu (tôi không chắc mã nào được tạo cho generic).
supercat

Trong Generics bạn có thể nhận được những vấn đề phức tạp xung quanh các loại nullable và giải quyết tình trạng quá tải ...
ErikE

3

Có thể là cái này bài viết giúp bạn:

Nếu tôi nhớ không nhầm thì 'Không có gì' trong VB có nghĩa là "giá trị mặc định". Đối với một loại giá trị, đó là giá trị mặc định, đối với một loại tham chiếu, sẽ là giá trị rỗng. Vì vậy, không gán gì cho một cấu trúc, không có vấn đề gì cả.


3
Điều này không trả lời câu hỏi.
David Heffernan

Không, nó không làm rõ điều gì. Câu hỏi là tất cả về <>toán tử trong VB và cách nó hoạt động trên các kiểu nullable.
David Heffernan

2

Đây là một sự kỳ lạ nhất định của VB.

Trong VB, nếu bạn muốn so sánh hai kiểu nullable, bạn nên sử dụng Nullable.Equals() .

Trong ví dụ của bạn, nó phải là:

Dim x As System.Nullable(Of Decimal) = Nothing
Dim y As System.Nullable(Of Decimal) = Nothing

y = 5
If Not Nullable.Equals(x, y) Then
    Console.WriteLine("true")
Else
    Console.WriteLine("false")
End If

5
Thật "dị" khi không quen. Xem câu trả lời do Pieter Geerkens đưa ra.
rskar

Tôi cũng nghĩ rằng thật kỳ lạ khi VB không tái tạo hành vi của Nullable<>.Equals(). Người ta có thể mong đợi nó hoạt động theo cùng một cách (đó là những gì C # làm).
Matthew Watson,

Kỳ vọng, giống như những gì "người ta có thể mong đợi", là về những gì một người đã trải qua. C # được thiết kế với mong đợi của người dùng Java. Java được thiết kế với mong đợi của người dùng C / C ++. Dù tốt hơn hay tệ hơn, VB.NET được thiết kế với mong đợi của người dùng VB6. Thêm thức ăn để suy nghĩ tại stackoverflow.com/questions/14837209/…stackoverflow.com/questions/10176737/…
rskar,

1
@MatthewWatson Định nghĩa của Nullablekhông tồn tại trong các phiên bản đầu tiên của .NET, nó được tạo ra sau khi C # và VB.NET đã ra mắt được một thời gian và đã xác định hành vi lan truyền rỗng của chúng. Bạn có thực sự mong đợi ngôn ngữ sẽ phù hợp với một loại đã không được tạo ra trong vài năm không? Từ quan điểm của một lập trình viên VB.NET, đó là Nullable.Equals không phù hợp với ngôn ngữ, chứ không phải ngược lại. (Cho rằng C # và VB đều sử dụng cùng một Nullableđịnh nghĩa, không có cách nào cho nó phù hợp với cả hai ngôn ngữ.)
Servy

0

Mã VB của bạn chỉ đơn giản là không chính xác - nếu bạn thay đổi "x <> y" thành "x = y", kết quả là bạn vẫn có "false". Cách phổ biến nhất của biểu thức này đối với các trường hợp nullable là "Không phải x.Equals (y)" và điều này sẽ mang lại hành vi tương tự như "x! = Y" trong C #.


1
Trừ khi xnothing, trong trường hợp đó x.Equals(y)sẽ ném một ngoại lệ.
Servy,

@Servy: Tình cờ gặp lại điều này (nhiều năm sau) và nhận thấy rằng tôi đã không sửa bạn - "x.Equals (y)" sẽ không đưa ra ngoại lệ cho thể hiện kiểu nullable 'x'. Các kiểu Nullable được trình biên dịch xử lý khác nhau.
Dave Doknjas 14/09/18

Cụ thể, một cá thể nullable được khởi tạo thành 'null' không thực sự là một biến được đặt thành null, mà là một cá thể System.Nullable không có bộ giá trị.
Dave Doknjas 14/09/18
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.