If (items! = Null) có thừa trước foreach (T item trong các item) không?


103

Tôi thường gặp mã như sau:

if ( items != null)
{
   foreach(T item in items)
   {
        //...
   }
}

Về cơ bản, ifđiều kiện đảm bảo rằng foreachkhối sẽ thực thi chỉ khi itemskhông rỗng. Tôi đang tự hỏi liệu ifđiều kiện có thực sự cần thiết hay foreachsẽ xử lý trường hợp này nếu items == null.

Ý tôi là, tôi có thể viết đơn giản không

foreach(T item in items)
{
    //...
}

mà không cần lo lắng về việc liệu itemscó null hay không? Là ifthừa điều kiện? Hoặc này phụ thuộc vào loại của itemshoặc có thể trên Tkhông?



1
@ câu trả lời kjbartel của (ít " stackoverflow.com/a/32134295/401246 " là giải pháp tốt nhất, bởi vì nó không: a) liên quan đến việc suy giảm hiệu suất của (ngay cả khi không null) khái quát hóa toàn bộ vòng lặp để màn hình LCD của Enumerable(như sử dụng ??sẽ ), b) yêu cầu thêm Phương thức mở rộng vào mọi Dự án, hoặc c) yêu cầu tránh null IEnumerables (Pffft! Puh-LEAZE! SMH.) để bắt đầu bằng (cuz, nullcó nghĩa là Không / A, trong khi danh sách trống có nghĩa là, nhưng là hiện tại, tốt, trống !, tức là Empl. có thể có Hoa hồng không phải là Bán hàng hoặc trống cho Bán hàng khi họ chưa kiếm được bất kỳ khoản nào).
Tom

Câu trả lời:


115

Bạn vẫn cần kiểm tra xem (items! = Null) nếu không bạn sẽ nhận được NullReferenceException. Tuy nhiên, bạn có thể làm như sau:

List<string> items = null;  
foreach (var item in items ?? new List<string>())
{
    item.Dump();
}

nhưng bạn có thể kiểm tra hiệu suất của nó. Vì vậy, tôi vẫn thích có if (items! = Null) trước.

Dựa trên gợi ý Lippert của Eric, tôi đã đổi mã thành:

List<string> items = null;  
foreach (var item in items ?? Enumerable.Empty<string>())
{
    item.Dump();
}

31
Ý tưởng dễ thương; mảng trống sẽ thích hợp hơn vì nó tiêu tốn ít bộ nhớ hơn và tạo ra ít áp lực bộ nhớ hơn. Enumerable.Empty <string> thậm chí còn thích hợp hơn vì nó lưu vào bộ nhớ cache mảng trống mà nó tạo ra và sử dụng lại nó.
Eric Lippert

5
Tôi hy vọng mã thứ hai sẽ chậm hơn. Nó làm suy giảm trình tự thành một trình tự IEnumerable<T>mà sau đó sẽ làm suy giảm trình điều tra thành một giao diện làm cho việc lặp lại chậm hơn. Thử nghiệm của tôi cho thấy sự suy giảm hệ số 5 đối với việc lặp qua một mảng int.
CodesInChaos

11
@CodeInChaos: Bạn có thường thấy rằng tốc độ liệt kê một chuỗi trống là điểm nghẽn hiệu suất trong chương trình của bạn không?
Eric Lippert

14
Nó không chỉ làm giảm tốc độ liệt kê của dãy trống mà còn của cả dãy đầy đủ. Và nếu trình tự đủ dài, nó có thể quan trọng. Đối với hầu hết các mã, chúng ta nên chọn mã thành ngữ. Nhưng hai cách phân bổ mà bạn đã đề cập sẽ là một vấn đề về hiệu suất trong một số ít trường hợp hơn.
CodesInChaos

15
@CodeInChaos: Ah, tôi thấy ý của bạn bây giờ. Khi trình biên dịch có thể phát hiện ra rằng "foreach" đang lặp qua Danh sách <T> hoặc một mảng thì nó có thể tối ưu hóa foreach để sử dụng các kiểu liệt kê giá trị hoặc thực sự tạo ra một vòng lặp "for". Khi bị buộc phải liệt kê trên một danh sách hoặc dãy trống, nó phải quay trở lại codegen "mẫu số chung thấp nhất", điều này trong một số trường hợp có thể chậm hơn và tạo ra nhiều áp lực bộ nhớ hơn. Đây là một điểm tinh tế nhưng tuyệt vời. Tất nhiên, đạo đức của câu chuyện là - như mọi khi - nếu bạn gặp vấn đề về hiệu suất thì hãy lập hồ sơ để tìm ra nút thắt thực sự là gì.
Eric Lippert

68

Sử dụng C # 6, bạn có thể sử dụng toán tử có điều kiện mới cùng với List<T>.ForEach(Action<T>)(hoặc IEnumerable<T>.ForEachphương thức mở rộng của riêng bạn ).

List<string> items = null;
items?.ForEach(item =>
{
    // ...
});

Câu trả lời tao nhã. Cảm ơn!
Steve

2
Đây là giải pháp tốt nhất, vì nó không: a) liên quan đến sự suy giảm hiệu suất của (ngay cả khi không null) tổng quát hóa toàn bộ vòng lặp lên màn hình LCD của Enumerable(như cách sử dụng ??), b) yêu cầu thêm một Phương thức mở rộng cho mọi Dự án, hoặc c ) đòi hỏi phải tránh null IEnumerables (Pffft! Puh-LEAZE! SMH.) để bắt đầu với (cuz, nullnghĩa là N / A, trong khi phương tiện danh sách trống, nó Appl. nhưng hiện nay, tốt, trống rỗng !, tức là một empl. có thể có hoa hồng mà của N / A cho Không phải Bán hàng hoặc trống cho Bán hàng khi họ chưa kiếm được bất kỳ).
Tom

6
@Tom: Nó giả định rằng đó itemsList<T>mặc dù, thay vì chỉ là bất kỳ IEnumerable<T>. (Hoặc có một phương pháp tiện ích mở rộng tùy chỉnh, mà bạn đã nói rằng bạn không muốn có ...) Ngoài ra, tôi muốn nói rằng thực sự không đáng để thêm 11 nhận xét về cơ bản nói rằng bạn thích một câu trả lời cụ thể.
Jon Skeet

2
@Tom: Tôi thực sự không khuyến khích bạn làm như vậy trong tương lai. Hãy tưởng tượng nếu mọi người không đồng ý với nhận xét của bạn sau đó thêm nhận xét của họ vào tất cả nhận xét của bạn . (Hãy tưởng tượng tôi đã viết câu trả lời của mình ở đây nhưng 11 lần.) Đây chỉ đơn giản là cách sử dụng Stack Overflow không hiệu quả.
Jon Skeet

1
Tôi cũng cho rằng sẽ có một cú đánh hiệu suất gọi đại biểu so với một tiêu chuẩn foreach. Riêng đối với một Danh sách mà tôi nghĩ sẽ được chuyển đổi thành một forvòng lặp.
kjbartel

37

Bài học thực sự ở đây phải là một chuỗi hầu như không bao giờ được để trống ngay từ đầu . Đơn giản chỉ cần làm cho nó trở thành bất biến trong tất cả các chương trình của bạn mà nếu bạn có một chuỗi, nó không bao giờ là null. Nó luôn được khởi tạo là dãy trống hoặc một số dãy chính hãng khác.

Nếu một chuỗi không bao giờ là null thì rõ ràng bạn không cần kiểm tra nó.


1
Còn nếu bạn lấy chuỗi từ dịch vụ WCF thì sao? Nó có thể là null, phải không?
Nawaz

4
@Nawaz: Nếu tôi có một dịch vụ WCF trả lại cho tôi các chuỗi rỗng với ý định chúng là các chuỗi trống thì tôi sẽ báo cáo điều đó với họ như một lỗi. Điều đó nói rằng: nếu bạn phải đối phó với đầu ra được hình thành xấu của các dịch vụ được cho là lỗi, thì vâng, bạn phải đối phó với nó bằng cách kiểm tra giá trị rỗng.
Eric Lippert

7
Tất nhiên, trừ khi rỗng và rỗng có nghĩa là những thứ hoàn toàn khác nhau. Đôi khi điều đó hợp lệ cho các chuỗi.
cấu hình

@Nawaz Làm thế nào về DataTable.Rows trả về null thay vì một tập hợp rỗng. Có lẽ đó là một lỗi?
Neil B

@ câu trả lời kjbartel của (ít " stackoverflow.com/a/32134295/401246 " là giải pháp tốt nhất, bởi vì nó không: a) liên quan đến việc suy giảm hiệu suất của (ngay cả khi không null) khái quát hóa toàn bộ vòng lặp để màn hình LCD của Enumerable(như sử dụng ??sẽ ), b) yêu cầu thêm Phương thức mở rộng vào mọi Dự án, hoặc c) yêu cầu tránh null IEnumerables (Pffft! Puh-LEAZE! SMH.) để bắt đầu bằng (cuz, nullcó nghĩa là Không / A, trong khi danh sách trống có nghĩa là, nhưng là hiện tại, tốt, trống !, tức là Empl. có thể có Hoa hồng không phải là Bán hàng hoặc trống cho Bán hàng khi họ chưa kiếm được bất kỳ khoản nào).
Tom

10

Trên thực tế, có một yêu cầu tính năng trên @Connect đó: http://connect.microsoft.com/VisualStudio/feedback/details/93497/foreach-should-check-for-null

Và phản ứng khá hợp lý:

Tôi nghĩ rằng hầu hết các vòng lặp foreach được viết với mục đích lặp lại một tập hợp không rỗng. Nếu bạn thử lặp lại qua null, bạn sẽ nhận được ngoại lệ của mình để có thể sửa mã của mình.


Tôi đoán có những ưu và khuyết điểm cho điều này, vì vậy họ quyết định giữ nó như nó được thiết kế ngay từ đầu. xét cho cùng, foreach chỉ là một số đường cú pháp. nếu bạn đã gọi items.GetEnumerator () cũng sẽ gặp sự cố nếu các mục rỗng, vì vậy bạn phải kiểm tra điều đó trước.
Marius Bancila

6

Bạn luôn có thể kiểm tra nó với danh sách rỗng ... nhưng đây là những gì tôi tìm thấy trên trang web msdn

foreach-statement:
    foreach   (   type   identifier   in   expression   )   embedded-statement 

Nếu biểu thức có giá trị null, một System.NullReferenceException được ném ra.


2

Nó không phải là superflous. Trong thời gian chạy, các mục sẽ được chuyển sang IEnumerable và phương thức GetEnumerator của nó sẽ được gọi. Điều đó sẽ gây ra việc tham khảo các mục sẽ không thành công


1
1) Trình tự sẽ không nhất thiết phải được chuyển thành IEnumerablevà 2) Đó là một quyết định thiết kế để thực hiện nó. C # có thể dễ dàng chèn nullkiểm tra đó nếu các nhà phát triển coi đó là một ý tưởng hay.
CodesInChaos

2

Bạn có thể đóng gói kiểm tra null trong một phương thức mở rộng và sử dụng lambda:

public static class EnumerableExtensions {
  public static void ForEach<T>(this IEnumerable<T> self, Action<T> action) {
    if (self != null) {
      foreach (var element in self) {
        action(element);
      }
    }
  }
}

Mã trở thành:

items.ForEach(item => { 
  ...
});

Nếu có thể ngắn gọn hơn nữa nếu bạn chỉ muốn gọi một phương thức nhận một mục và trả về void:

items.ForEach(MethodThatTakesAnItem);

1

Bạn cần điều này. Bạn sẽ nhận được một ngoại lệ khiforeach truy cập vùng chứa để thiết lập lặp lại.

Dưới các bìa, foreachsử dụng một giao diện được triển khai trên lớp bộ sưu tập để thực hiện lặp lại. Giao diện tương đương chung là ở đây .

Câu lệnh foreach của ngôn ngữ C # (cho mỗi ngôn ngữ trong Visual Basic) ẩn sự phức tạp của các điều tra viên. Do đó, nên sử dụng foreach thay vì thao tác trực tiếp với điều tra viên.


1
Cũng như một lưu ý về mặt kỹ thuật, nó không sử dụng giao diện, nó sử dụng kiểu gõ vịt: blog.msdn.com/b/kcwalina/archive/2007/07/18/ducknotation.aspx các giao diện đảm bảo rằng các phương thức và thuộc tính phù hợp ở đó mặc dù, và hỗ trợ hiểu biết về ý định. cũng như sử dụng bên ngoài foreach ...
ShuggyCoUk

0

Việc kiểm tra là cần thiết, bởi vì nếu bộ sưu tập là null, foreach sẽ ném ra một NullReferenceException. Nó thực sự khá đơn giản để thử nó.

List<string> items = null;
foreach(var item in items)
{
   Console.WriteLine(item);
}

0

thứ hai sẽ ném một NullReferenceExceptiontin nhắnObject reference not set to an instance of an object.


0

Như đã đề cập ở đây, bạn cần phải kiểm tra xem nó có phải không.

Không sử dụng một biểu thức đánh giá là null.


0

Trong C # 6, bạn có thể viết sth như thế này:

// some string from file or UI, i.e.:
// a) string s = "Hello, World!";
// b) string s = "";
// ...
var items = s?.Split(new char[] { ',', '!', ' ' }) ?? Enumerable.Empty<string>();  
foreach (var item in items)
{
    //..
}

Về cơ bản đó là giải pháp của Vlad Bezden nhưng sử dụng dấu ?? biểu thức để luôn tạo ra một mảng không rỗng và do đó tồn tại trước foreach hơn là có dấu kiểm này bên trong dấu ngoặc foreach.

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.