Được ưu tiên: Không thể <T> .HasValue hoặc Nullable <T>! = Nullable?


437

Tôi luôn luôn sử dụng Nullable<>.HasValuevì tôi thích ngữ nghĩa. Tuy nhiên, gần đây tôi đang làm việc trên cơ sở mã hiện tại của người khác, nơi họ chỉ sử dụng Nullable<> != nullriêng.

Có một lý do để sử dụng cái này hơn cái kia, hay nó hoàn toàn là sở thích?

  1. int? a;
    if (a.HasValue)
        // ...

so với

  1. int? b;
    if (b != null)
        // ...

9
Tôi đã hỏi một câu hỏi tương tự ... nhận được một số câu trả lời hay: stackoverflow.com/questions/633286/ trên
nailitdown

3
Cá nhân , tôi sử dụng HasValuevì tôi nghĩ các từ có xu hướng dễ đọc hơn các ký hiệu. Tất cả tùy thuộc vào bạn, và những gì phù hợp với phong cách hiện có của bạn.
Jake Petroules

1
.HasValuecó ý nghĩa hơn vì nó biểu thị loại là loại T?chứ không phải là loại có thể là null, chẳng hạn như chuỗi.
dùng3791372

Câu trả lời:


476

Trình biên dịch thay thế các so sánh null bằng một cuộc gọi đến HasValue, vì vậy không có sự khác biệt thực sự. Chỉ cần làm bất cứ điều gì dễ đọc hơn / có ý nghĩa hơn với bạn và đồng nghiệp của bạn.


86
Tôi sẽ thêm vào đó "cái nào phù hợp hơn / theo phong cách mã hóa hiện có."
Josh Lee

20
Ồ Tôi ghét đường cú pháp này. int? x = nullcho tôi ảo tưởng rằng một thể hiện nullable là một kiểu tham chiếu. Nhưng sự thật là Nullable <T> là một loại giá trị. Tôi cảm thấy muốn có một NullReferenceException để làm : int? x = null; Use(x.HasValue).
KFL

11
@KFL Nếu đường cú pháp làm phiền bạn, chỉ cần sử dụng Nullable<int>thay vì int?.
Cole Johnson

24
Trong giai đoạn đầu tạo ứng dụng, bạn có thể nghĩ rằng việc sử dụng loại giá trị không thể sử dụng để lưu trữ một số dữ liệu là điều không cần thiết, chỉ sau một thời gian bạn cần một lớp thích hợp cho mục đích của mình. Đã viết mã gốc để so sánh với null thì có một lợi thế là bạn không cần tìm kiếm / thay thế mọi cuộc gọi đến HasValue () bằng một so sánh null.
Anders

14
Thật là ngớ ngẩn khi phàn nàn về việc có thể đặt Nullable thành null hoặc so sánh nó với null được gọi là Nullable . Vấn đề là mọi người đang kết hợp "loại tham chiếu" với "có thể là null", nhưng đó là một sự nhầm lẫn về khái niệm. C # trong tương lai sẽ có các loại tham chiếu không thể rỗng.
Jim Balter

48

Tôi thích (a != null)để cú pháp phù hợp với các loại tham chiếu.


11
Đó là khá gây hiểu lầm, tất nhiên, vì Nullable<>không một loại tài liệu tham khảo.
Luaan

9
Có, nhưng thực tế thường rất ít quan trọng tại điểm bạn không kiểm tra.
cbp

31
Nó chỉ gây hiểu lầm cho khái niệm nhầm lẫn. Sử dụng một cú pháp nhất quán cho hai loại khác nhau không có nghĩa là chúng cùng loại. C # có các loại tham chiếu nullable (tất cả các loại tham chiếu hiện không thể thực hiện được, nhưng điều đó sẽ thay đổi trong tương lai) và các loại giá trị nullable. Sử dụng một cú pháp nhất quán cho tất cả các loại nullable có ý nghĩa. Không có nghĩa là các loại giá trị nullable là các loại tham chiếu hoặc các loại tham chiếu nullable là các loại giá trị.
Jim Balter

Tôi thích HasValuebởi vì nó dễ đọc hơn!= null
Konrad

Tính nhất quán mã hóa dễ đọc hơn nếu bạn không trộn lẫn các kiểu viết khác nhau của cùng một mã. Vì không phải tất cả các địa điểm đều có thuộc tính .HasValue nên nó được sử dụng! = Null để tăng tính nhất quán. Trong tôi.
ColacX

21

Tôi đã thực hiện một số nghiên cứu về điều này bằng cách sử dụng các phương thức khác nhau để gán giá trị cho một int nullable. Đây là những gì đã xảy ra khi tôi làm nhiều việc khác nhau. Nên làm rõ những gì đang xảy ra. Hãy ghi nhớ: Nullable<something>hoặc tốc ký something?là một cấu trúc mà trình biên dịch dường như đang thực hiện rất nhiều công việc để cho chúng tôi sử dụng với null như thể nó là một lớp.
Như bạn sẽ thấy bên dưới, SomeNullable == nullSomeNullable.HasValuesẽ luôn trả về đúng hoặc sai dự kiến. Mặc dù không được trình bày dưới đây, nhưng SomeNullable == 3cũng hợp lệ (giả sử someNullable là một int?).
Trong khi SomeNullable.Valuechúng ta gặp lỗi thời gian chạy nếu chúng ta được gán nullcho SomeNullable. Trên thực tế đây là trường hợp duy nhất mà nullables có thể gây ra sự cố cho chúng tôi, nhờ vào sự kết hợp của các toán tử quá tải, quá tảiobject.Equals(obj) phương pháp, và tối ưu hóa trình biên dịch và kinh doanh khỉ.

Dưới đây là mô tả về một số mã tôi đã chạy và đầu ra mà nó tạo ra trong nhãn:

int? val = null;
lbl_Val.Text = val.ToString(); //Produced an empty string.
lbl_ValVal.Text = val.Value.ToString(); //Produced a runtime error. ("Nullable object must have a value.")
lbl_ValEqNull.Text = (val == null).ToString(); //Produced "True" (without the quotes)
lbl_ValNEqNull.Text = (val != null).ToString(); //Produced "False"
lbl_ValHasVal.Text = val.HasValue.ToString(); //Produced "False"
lbl_NValHasVal.Text = (!(val.HasValue)).ToString(); //Produced "True"
lbl_ValValEqNull.Text = (val.Value == null).ToString(); //Produced a runtime error. ("Nullable object must have a value.")
lbl_ValValNEqNull.Text = (val.Value != null).ToString(); //Produced a runtime error. ("Nullable object must have a value.")

Ok, hãy thử phương thức khởi tạo tiếp theo:

int? val = new int?();
lbl_Val.Text = val.ToString(); //Produced an empty string.
lbl_ValVal.Text = val.Value.ToString(); //Produced a runtime error. ("Nullable object must have a value.")
lbl_ValEqNull.Text = (val == null).ToString(); //Produced "True" (without the quotes)
lbl_ValNEqNull.Text = (val != null).ToString(); //Produced "False"
lbl_ValHasVal.Text = val.HasValue.ToString(); //Produced "False"
lbl_NValHasVal.Text = (!(val.HasValue)).ToString(); //Produced "True"
lbl_ValValEqNull.Text = (val.Value == null).ToString(); //Produced a runtime error. ("Nullable object must have a value.")
lbl_ValValNEqNull.Text = (val.Value != null).ToString(); //Produced a runtime error. ("Nullable object must have a value.")

Tất cả giống như trước đây. Hãy nhớ rằng việc khởi tạo với int? val = new int?(null);, với null được truyền cho hàm tạo, sẽ tạo ra lỗi thời gian MÁY TÍNH, vì GIÁ TRỊ của đối tượng nullable KHÔNG thể null. Nó chỉ là đối tượng trình bao bọc chính nó có thể bằng null.

Tương tự như vậy, chúng tôi sẽ nhận được một lỗi thời gian biên dịch từ:

int? val = new int?();
val.Value = null;

không đề cập đến dù sao đó val.Valuecũng là một tài sản chỉ đọc, có nghĩa là chúng ta thậm chí không thể sử dụng cái gì đó như:

val.Value = 3;

nhưng một lần nữa, các toán tử chuyển đổi ẩn quá tải đa hình cho phép chúng ta làm:

val = 3;

Không cần phải lo lắng về polysomthing whatchamacallits, miễn là nó hoạt động phải không? :)


5
"Hãy ghi nhớ: Không thể <thứ gì đó> hoặc tốc ký gì đó? Là một lớp học." Cái này sai! Nullable <T> là một cấu trúc. Nó quá tải Toán tử bằng và toán tử == để trả về giá trị true khi so sánh với null. Trình biên dịch không có công việc ưa thích cho so sánh này.
andrewjs

1
@andrewjs - Bạn đúng rằng đó là một cấu trúc (không phải là một lớp), nhưng bạn đã sai khi nó quá tải toán tử ==. Nếu bạn nhập Nullable<X>vào VisualStudio 2013 và F12, bạn sẽ thấy rằng nó chỉ quá tải chuyển đổi sang và từ XEquals(object other)phương thức. Tuy nhiên, tôi nghĩ toán tử == sử dụng phương thức đó theo mặc định, vì vậy hiệu ứng là như nhau. Tôi thực sự có ý định cập nhật câu trả lời này trên thực tế đó một thời gian, nhưng tôi lười biếng và / hoặc bận rộn. Nhận xét này sẽ phải làm ngay bây giờ :)
Perrin Larson

Tôi đã kiểm tra nhanh thông qua ildasm và bạn nói đúng về trình biên dịch đang thực hiện một số phép thuật; việc so sánh một đối tượng <T> Nullable với null thực tế chuyển thành một cuộc gọi đến HasValue. Hấp dẫn!
andrewjs

3
@andrewjs Trên thực tế, trình biên dịch thực hiện rất nhiều công việc để tối ưu hóa nullables. Ví dụ: nếu bạn gán một giá trị cho loại nullable, nó thực sự sẽ không phải là nullable (ví dụ int? val = 42; val.GetType() == typeof(int):). Vì vậy, không chỉ null là một cấu trúc có thể bằng null, nó cũng thường không phải là một nullable! : D Tương tự, khi bạn đóng một giá trị vô giá trị, bạn là quyền anh int, không phải int?- và khi int?không có giá trị, bạn sẽ nhận được nullthay vì giá trị không thể đóng hộp. Về cơ bản, điều đó có nghĩa là hiếm khi có bất kỳ chi phí nào từ việc sử dụng nullable đúng cách :)
Luaan

1
@JimBalter Thật sao? Thật là thú vị. Vì vậy, trình lược tả bộ nhớ cho bạn biết gì về một trường nullable trong một lớp? Làm thế nào để bạn khai báo một loại giá trị kế thừa từ một loại giá trị khác trong C #? Làm thế nào để bạn khai báo loại nullable của riêng bạn hoạt động giống như loại nullable của .NET? Từ khi nào là Nullmột loại trong .NET? Bạn có thể chỉ ra một phần trong đặc tả CLR / C # trong đó nói không? Nullables được xác định rõ trong đặc tả CLR, hành vi của chúng không phải là "thực hiện trừu tượng hóa" - đó là một hợp đồng . Nhưng nếu điều tốt nhất bạn có thể làm là tấn công hominem quảng cáo, hãy tận hưởng.
Luaan

13

Trong VB.Net. KHÔNG sử dụng "IsNot nothing" khi bạn có thể sử dụng ".HasValue". Tôi vừa giải quyết một "Hoạt động có thể làm mất ổn định thời gian chạy" Lỗi tin cậy trung bình bằng cách thay thế "IsNot nothing" bằng ".HasValue" tại một điểm. Tôi thực sự không hiểu tại sao, nhưng một cái gì đó đang xảy ra khác nhau trong trình biên dịch. Tôi cho rằng "! = Null" trong C # có thể có cùng một vấn đề.


8
Tôi thích HasValuevì dễ đọc. IsNot Nothingthực sự là một biểu hiện xấu xí (vì phủ định kép).
Stefan Steinegger

12
@steffan "IsNot nothing" không phải là phủ định kép. "Không có gì" không phải là một tiêu cực, đó là một số lượng riêng biệt, thậm chí bên ngoài lĩnh vực lập trình. "Số lượng này không là gì cả." về mặt ngữ pháp, chính xác giống như nói "Đại lượng này không phải là số không". và không phải là một tiêu cực kép.
jmbpiano

5
Không phải là tôi không muốn không đồng ý với sự vắng mặt của sự thật ở đây, mà thôi nào. IsNot Không có gì rõ ràng, tốt, quá tiêu cực. Tại sao không viết một cái gì đó tích cực và rõ ràng như HasValue? Đây không phải là một bài kiểm tra ngữ pháp, nó là mã hóa, trong đó mục tiêu chính là sự rõ ràng.
Trò chơi Randy

3
jmbpiano: Tôi đồng ý rằng nó không phải là phủ định kép, nhưng đó là một phủ định duy nhất và nó gần như xấu xí và không rõ ràng như một biểu hiện tích cực đơn giản.
Kaveh Hadjari

0

Nếu bạn sử dụng linq và muốn giữ mã của bạn ngắn, tôi khuyên bạn nên luôn luôn sử dụng !=null

Và đây là lý do:

Hãy tưởng tượng chúng ta có một số lớp Foovới một biến kép không thểSomeDouble

public class Foo
{
    public double? SomeDouble;
    //some other properties
}   

Nếu ở đâu đó trong mã của chúng tôi, chúng tôi muốn nhận được tất cả Foo với giá trị someDouble khác từ bộ sưu tập Foo (giả sử một số foos trong bộ sưu tập cũng có thể là null), chúng tôi sẽ kết thúc với ít nhất ba cách để viết hàm của chúng tôi (nếu chúng tôi sử dụng C # 6):

public IEnumerable<Foo> GetNonNullFoosWithSomeDoubleValues(IEnumerable<Foo> foos)
{
     return foos.Where(foo => foo?.SomeDouble != null);
     return foos.Where(foo=>foo?.SomeDouble.HasValue); // compile time error
     return foos.Where(foo=>foo?.SomeDouble.HasValue == true); 
     return foos.Where(foo=>foo != null && foo.SomeDouble.HasValue); //if we don't use C#6
}

Và trong tình huống này, tôi khuyên bạn nên luôn luôn đi cho ngắn hơn


2
Có, foo?.SomeDouble.HasValuelà một lỗi thời gian biên dịch (không phải là "ném" trong thuật ngữ của tôi) trong ngữ cảnh đó bởi vì kiểu của nó là bool?, không chỉ bool. ( .WherePhương thức muốn a Func<Foo, bool>.) Tất nhiên, nó được phép làm (foo?.SomeDouble).HasValuevì nó có kiểu bool. Đây là những gì dòng đầu tiên của bạn được "dịch" sang bên trong bởi trình biên dịch C # (ít nhất là chính thức).
Jeppe Stig Nielsen

-6

Câu trả lời chung và quy tắc chung: nếu bạn có một tùy chọn (ví dụ: viết tuần tự tùy chỉnh) để xử lý Nullable trong các đường ống khác nhau object- và sử dụng các thuộc tính cụ thể của chúng - thực hiện và sử dụng các thuộc tính cụ thể của Nullable. Vì vậy, từ quan điểm tư duy nhất quán HasValuenên được ưu tiên. Suy nghĩ nhất quán có thể giúp bạn viết mã tốt hơn không mất quá nhiều thời gian chi tiết. Ví dụ, phương pháp thứ hai sẽ hiệu quả hơn nhiều lần (chủ yếu là do trình biên dịch nội tuyến và quyền anh nhưng số vẫn rất biểu cảm):

public static bool CheckObjectImpl(object o)
{
    return o != null;
}

public static bool CheckNullableImpl<T>(T? o) where T: struct
{
    return o.HasValue;
}

Kiểm tra điểm chuẩn:

BenchmarkDotNet=v0.10.5, OS=Windows 10.0.14393
Processor=Intel Core i5-2500K CPU 3.30GHz (Sandy Bridge), ProcessorCount=4
Frequency=3233539 Hz, Resolution=309.2587 ns, Timer=TSC
  [Host] : Clr 4.0.30319.42000, 64bit RyuJIT-v4.6.1648.0
  Clr    : Clr 4.0.30319.42000, 64bit RyuJIT-v4.6.1648.0
  Core   : .NET Core 4.6.25009.03, 64bit RyuJIT


        Method |  Job | Runtime |       Mean |     Error |    StdDev |        Min |        Max |     Median | Rank |  Gen 0 | Allocated |
-------------- |----- |-------- |-----------:|----------:|----------:|-----------:|-----------:|-----------:|-----:|-------:|----------:|
   CheckObject |  Clr |     Clr | 80.6416 ns | 1.1983 ns | 1.0622 ns | 79.5528 ns | 83.0417 ns | 80.1797 ns |    3 | 0.0060 |      24 B |
 CheckNullable |  Clr |     Clr |  0.0029 ns | 0.0088 ns | 0.0082 ns |  0.0000 ns |  0.0315 ns |  0.0000 ns |    1 |      - |       0 B |
   CheckObject | Core |    Core | 77.2614 ns | 0.5703 ns | 0.4763 ns | 76.4205 ns | 77.9400 ns | 77.3586 ns |    2 | 0.0060 |      24 B |
 CheckNullable | Core |    Core |  0.0007 ns | 0.0021 ns | 0.0016 ns |  0.0000 ns |  0.0054 ns |  0.0000 ns |    1 |      - |       0 B |

Mã điểm chuẩn:

public class BenchmarkNullableCheck
{
    static int? x = (new Random()).Next();

    public static bool CheckObjectImpl(object o)
    {
        return o != null;
    }

    public static bool CheckNullableImpl<T>(T? o) where T: struct
    {
        return o.HasValue;
    }

    [Benchmark]
    public bool CheckObject()
    {
        return CheckObjectImpl(x);
    }

    [Benchmark]
    public bool CheckNullable()
    {
        return CheckNullableImpl(x);
    }
}

https://github.com/dotnet/BenchmarkDotNet đã được sử dụng

PS . Mọi người nói rằng lời khuyên "thích HasValue vì suy nghĩ nhất quán" không liên quan và vô dụng. Bạn có thể dự đoán hiệu suất của điều này?

public static bool CheckNullableGenericImpl<T>(T? t) where T: struct
{
    return t != null; // or t.HasValue?
}

PPS Mọi người tiếp tục trừ nhưng không ai cố gắng dự đoán hiệu suất của CheckNullableGenericImpl. Và có trình biên dịch sẽ không giúp bạn thay thế !=nullbằng HasValue. HasValuenên được sử dụng trực tiếp nếu bạn quan tâm đến hiệu suất.


2
CheckObjectImpl Hộp của bạn vô giá trị thành một object, trong khi CheckNullableImplkhông sử dụng quyền anh. Do đó, việc so sánh là rất không mong muốn. Không chỉ nó không phải là giá vé, nó cũng vô dụng bởi vì, như đã lưu ý trong câu trả lời được chấp nhận , trình biên dịch sẽ viết lại !=thành HasValueanyway.
GSerg

2
Các độc giả không bỏ qua cấu trúc bản chất của Nullable<T>, bạn làm (bằng cách đóng nó thành một object). Khi bạn áp dụng != nullvới một nullable ở bên trái, không có quyền anh nào xảy ra vì sự hỗ trợ !=cho nullables hoạt động ở cấp độ trình biên dịch. Nó khác khi bạn ẩn nullable khỏi trình biên dịch bằng cách lần đầu tiên đóng nó thành một object. Cả CheckObjectImpl(object o)nguyên tắc và điểm chuẩn của bạn đều không có ý nghĩa về nguyên tắc.
GSerg

3
Vấn đề của tôi là tôi quan tâm đến chất lượng nội dung trên trang web này. Những gì bạn đăng là sai lệch hoặc sai. Nếu bạn đang cố gắng trả lời câu hỏi của OP, sau đó câu trả lời của bạn là ngoài phẳng sai, đó là dễ dàng để chứng minh bằng cách thay thế các cuộc gọi đến CheckObjectImplvới nó cơ thể bên trong CheckObject. Tuy nhiên, những bình luận mới nhất của bạn tiết lộ rằng trên thực tế bạn có một câu hỏi hoàn toàn khác khi bạn quyết định trả lời câu hỏi 8 tuổi này, điều này khiến câu trả lời của bạn bị sai lệch trong bối cảnh của câu hỏi ban đầu. Đó không phải là những gì OP đã hỏi về.
GSerg

3
Đặt mình vào vị trí của chàng trai tiếp theo googles what is faster != or HasValue. Anh ta đi đến câu hỏi này, duyệt qua câu trả lời của bạn, đánh giá cao điểm chuẩn của bạn và nói, "Gee, tôi sẽ không bao giờ sử dụng !=vì nó rõ ràng chậm hơn rất nhiều!" Đó là một kết luận rất sai lầm mà sau đó anh ta sẽ tiến hành lan truyền xung quanh. Đó là lý do tại sao tôi tin rằng câu trả lời của bạn là có hại - nó trả lời một câu hỏi sai và do đó đưa ra một kết luận sai trong người đọc không nghi ngờ. Xem xét những gì xảy ra khi bạn thay đổi CheckNullableImplthành cũngreturn o != null;Bạn sẽ nhận được kết quả điểm chuẩn tương tự.
GSerg

8
Tôi đang tranh luận với câu trả lời của bạn. Câu trả lời của bạn trông có vẻ giống như nó cho thấy sự khác biệt giữa !=HasValuekhi thực tế nó cho thấy sự khác biệt giữa object oT? o. Nếu bạn làm những gì tôi đề nghị, có nghĩa là, viết lại CheckNullableImplnhư public static bool CheckNullableImpl<T>(T? o) where T: struct { return o != null; }, bạn sẽ kết thúc với một chuẩn mực rõ ràng cho thấy !=là chậm hơn nhiều so với !=. Mà sẽ dẫn bạn đến kết luận rằng vấn đề câu trả lời của bạn mô tả không phải là về !=vs HasValuecả.
GSerg
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.