Kiểm tra null trong vòng lặp foreach


91

Có cách nào tốt hơn để thực hiện như sau:
Tôi cần kiểm tra giá trị null có xảy ra trên tệp hay không. Người đọc trước khi tiếp tục với vòng lặp

if (file.Headers != null)
{
  foreach (var h in file.Headers)
  {
   //set lots of properties & some other stuff
  }
}

Tóm lại, có vẻ hơi xấu khi viết foreach bên trong if do mức độ thụt lề xảy ra trong mã của tôi.

Là một cái gì đó sẽ đánh giá

foreach(var h in (file.Headers != null))
{
  //do stuff
}

khả thi?


3
Bạn có thể xem ở đây: stackoverflow.com/questions/6937407/…
Adrian Fâciu,

stackoverflow.com/questions/872323/… là một ý tưởng khác.
weismat

1
@AdrianFaciu Tôi nghĩ điều đó hoàn toàn khác. Câu hỏi kiểm tra xem bộ sưu tập có rỗng không trước khi thực hiện cho từng. Liên kết của bạn sẽ kiểm tra xem mục trong bộ sưu tập có rỗng không.
rikitikitik,


1
C # 8 chỉ có thể có một foreach điều kiện rỗng thuộc một số loại, tức là cú pháp như sau: foreach? (var i in collection) {} Tôi nghĩ rằng đó là một kịch bản đủ phổ biến để biện minh cho điều này và với những bổ sung có điều kiện rỗng gần đây cho ngôn ngữ mà nó có ý nghĩa ở đây?
mms

Câu trả lời:


121

Chỉ là một bổ sung thẩm mỹ nhẹ cho gợi ý của Rune, bạn có thể tạo phương pháp mở rộng của riêng mình:

public static IEnumerable<T> OrEmptyIfNull<T>(this IEnumerable<T> source)
{
    return source ?? Enumerable.Empty<T>();
}

Sau đó, bạn có thể viết:

foreach (var header in file.Headers.OrEmptyIfNull())
{
}

Đổi tên theo sở thích :)


75

Giả sử rằng loại phần tử trong tệp. Đầu đọc là T bạn có thể làm điều này

foreach(var header in file.Headers ?? Enumerable.Empty<T>()){
  //do stuff
}

điều này sẽ tạo ra một danh sách trống của T nếu tệp. Bộ đọc là null. Tuy nhiên, nếu loại tệp là loại bạn sở hữu, tôi sẽ cân nhắc việc thay đổi getter Headersthay thế. nulllà giá trị không xác định, vì vậy nếu có thể thay vì sử dụng null là "Tôi biết không có phần tử nào" khi null thực sự (/ ban đầu) nên được hiểu là "Tôi không biết nếu có bất kỳ phần tử nào" hãy sử dụng một tập hợp trống để hiển thị mà bạn biết không có phần tử nào trong tập hợp. Điều đó cũng sẽ KHÔ hơn vì bạn sẽ không phải kiểm tra null thường xuyên.

CHỈNH SỬA như một phần tiếp theo về đề xuất của Jons, bạn cũng có thể tạo một phương thức mở rộng thay đổi mã trên thành

foreach(var header in file.Headers.OrEmptyIfNull()){
  //do stuff
}

Trong trường hợp bạn không thể thay đổi getter, đây sẽ là ưu tiên của riêng tôi vì nó thể hiện ý định rõ ràng hơn bằng cách đặt tên cho hoạt động (OrEmptyIfNull)

Phương pháp tiện ích mở rộng được đề cập ở trên có thể khiến trình tối ưu hóa không thể phát hiện được. Cụ thể, những người có liên quan đến IList sử dụng quá tải phương pháp này có thể bị loại bỏ

public static IList<T> OrEmptyIfNull<T>(this IList<T> source)
{
    return source ?? Array.Empty<T>();
}

Câu trả lời của @ kjbartel (tại "stackoverflow.com/a/32134295/401246" 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) thoái hóa toàn bộ vòng lặp thành LCD của IEnumerable<T>(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à, nó có thể áp dụng nhưng hiện tại, tốt, trống rỗng !, tức là một Nhân viên 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

@Tom ngoài một lần kiểm tra rỗng, không có hình phạt nào đối với trường hợp điều tra viên không có giá trị. Tránh kiểm tra đó trong khi cũng đảm bảo rằng liệt kê không phải là null là không thể. Đoạn mã trên yêu cầu Tiêu đề phải IEnumerable hạn chế hơn foreachyêu cầu nhưng ít hạn chế hơn yêu cầu List<T>trong câu trả lời mà bạn liên kết đến. Cái nào có cùng một hình phạt về hiệu suất của việc kiểm tra xem liệu có thể liệt kê là null hay không.
Rune FS

Tôi đang căn cứ vấn đề "LCD" trên nhận xét của Eric Lippert về Câu trả lời của Vlad Bezden trong cùng một chủ đề với Câu trả lời của kjbartel: "@CodeInChaos: À, tôi hiểu ý 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 lại trên Danh sách <T> hoặc một mảng thì nó có thể tối ưu hóa foreach để sử dụng kiểu liệt kê kiểu giá trị hoặc thực sự tạo ra một vòng lặp "for". Khi buộc phải liệt kê trên một danh sách hoặc chuỗi trống, nó phải quay lại " mẫu số chung thấp nhất "codegen, 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 ....". Đồng ý nó yêu cầu List<T>.
Tom

@tom Tiền đề của câu trả lời là tệp đó. Đầu đọc là một IEnumerable <T>, trong trường hợp đó trình biên dịch không thể thực hiện tối ưu hóa. Tuy nhiên, khá dễ dàng để mở rộng giải pháp phương pháp mở rộng để tránh điều này. Xem chỉnh sửa
Rune FS

19

Thẳng thắn mà nói, tôi khuyên: cứ nullthử nghiệm đi. Một nullbài kiểm tra chỉ là một brfalsehoặc brfalse.s; mọi thứ khác sẽ liên quan đến công việc nhiều hơn nữa (bài kiểm tra, bài tập, các cuộc gọi phương pháp thêm, không cần thiết GetEnumerator(), MoveNext(), Dispose()trên iterator, vv).

Một ifbài kiểm tra rất đơn giản, rõ ràng và hiệu quả.


1
Tuy nhiên, bạn đưa ra một điểm thú vị Marc, tuy nhiên, tôi hiện đang tìm cách giảm mức độ thụt lề của mã. Nhưng tôi sẽ ghi nhớ nhận xét của bạn khi tôi cần lưu ý về hiệu suất.
Eminem,

3
Chỉ cần một ghi chú nhanh về Marc này .. nhiều năm sau khi tôi hỏi câu hỏi này và cần phải thực hiện một số cải tiến về hiệu suất, lời khuyên của bạn cực kỳ hữu ích. Cảm ơn bạn
Eminem

12

"if" trước khi lặp lại là tốt, một số ngữ nghĩa "đẹp" có thể làm cho mã của bạn khó đọc hơn.

Dù sao, nếu thụt lề làm phiền bạn, bạn có thể thay đổi if để kiểm tra:

if(file.Headers == null)  
   return;

và bạn sẽ chỉ đến được vòng lặp foreach khi có giá trị true trong thuộc tính headers.

một tùy chọn khác mà tôi có thể nghĩ đến là sử dụng toán tử null-coescing bên trong vòng lặp foreach của bạn và để tránh hoàn toàn việc kiểm tra null. mẫu vật:

List<int> collection = new List<int>();
collection = null;
foreach (var i in collection ?? Enumerable.Empty<int>())
{
    //your code here
}

(thay thế bộ sưu tập bằng đối tượng / loại thực sự của bạn)


Tùy chọn đầu tiên của bạn sẽ không hoạt động nếu có bất kỳ mã nào bên ngoài câu lệnh if.
rikitikitik,

Tôi đồng ý, câu lệnh if dễ thực hiện và rẻ so với việc tạo một danh sách mới, chỉ để làm đẹp mã.
Andreas Johansson,

10

Sử dụng Toán tử có điều kiện Null và ForEach () hoạt động nhanh hơn vòng lặp foreach tiêu chuẩn.
Bạn phải truyền bộ sưu tập vào Danh sách.

   listOfItems?.ForEach(item => // ... );

4
Vui lòng thêm một số giải thích xung quanh câu trả lời của bạn, trong đó nêu rõ lý do tại sao giải pháp này hoạt động, chứ không phải chỉ một mã one-liner
LordWilmore

giải pháp tốt nhất cho trường hợp của tôi
Josef Henn

3

Tôi đang sử dụng một phương pháp mở rộng nhỏ hay cho các trường hợp sau:

  public static class Extensions
  {
    public static IList<T> EnsureNotNull<T>(this IList<T> list)
    {
      return list ?? new List<T>();
    }
  }

Cho rằng Tiêu đề thuộc loại danh sách, bạn có thể làm như sau:

foreach(var h in (file.Headers.EnsureNotNull()))
{
  //do stuff
}

1
bạn có thể sử dụng các ??nhà điều hành và rút ngắn thời tuyên bố quay trở lạireturn list ?? new List<T>;
Rune FS

1
@wolfgangziegler, Nếu tôi hiểu chính xác thì xét nghiệm đối với nullmẫu của bạn file.Headers.EnsureNotNull() != nulllà không cần thiết và thậm chí là sai?
Remko Jansen,

0

Đối với một số trường hợp, tôi muốn một biến thể khác, chung chung hơn một chút, giả sử rằng, theo quy luật, các hàm tạo bộ sưu tập mặc định trả về các phiên bản trống.

Sẽ tốt hơn nếu đặt tên cho phương pháp này NewIfDefault. Nó có thể hữu ích không chỉ cho các bộ sưu tập, vì vậy ràng buộc kiểu IEnumerable<T>có thể là thừa.

public static TCollection EmptyIfDefault<TCollection, T>(this TCollection collection)
        where TCollection: class, IEnumerable<T>, new()
    {
        return collection ?? new TCollection();
    }
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.