Kiểm tra xem danh sách có trống không bằng LINQ


122

Cách "tốt nhất" (tính đến cả tốc độ và khả năng đọc) để xác định xem danh sách có trống không? Ngay cả khi danh sách thuộc loại IEnumerable<T>và không có thuộc tính Count.

Ngay bây giờ tôi đang xoay người giữa điều này:

if (myList.Count() == 0) { ... }

và điều này:

if (!myList.Any()) { ... }

Tôi đoán là tùy chọn thứ hai nhanh hơn, vì nó sẽ trả về kết quả ngay khi nhìn thấy mục đầu tiên, trong khi tùy chọn thứ hai (đối với IEnumerable) sẽ cần phải truy cập vào mọi mục để trả về số lượng.

Điều đó đang được nói, liệu tùy chọn thứ hai có dễ đọc đối với bạn không? Bạn thích cái nào? Hoặc bạn có thể nghĩ ra cách tốt hơn để kiểm tra danh sách trống không?

Phản hồi của Edit @ lassevk có vẻ hợp lý nhất, cùng với một chút kiểm tra thời gian chạy để sử dụng số lượng được lưu trong bộ nhớ cache nếu có thể, như sau:

public static bool IsEmpty<T>(this IEnumerable<T> list)
{
    if (list is ICollection<T>) return ((ICollection<T>)list).Count == 0;

    return !list.Any();
}

5
Nhiều hơn nữa tốt hơn không pha trộn iscastnhưng việc sử dụng asnullkiểm tra:ICollection<T> collection = list as ICollection<T>; if (collection != null) return colllection.Count;
abatishchev

2
Tại sao phải viết thêm một phương thức? Không phải là không list.Any()tương đương với list.IsEmpty? Phương pháp khuôn khổ nên được tối ưu hóa - chỉ nên viết một phương pháp mới nếu bạn nhận ra đó là một nút thắt cổ chai hoàn hảo.
dbkk

6
Có ai bận tâm đến việc đo lường hiệu suất trên các triển khai được đề xuất của họ hay mọi người chỉ đang đưa ra ý tưởng?
Michael Brown

Tôi đã đề xuất vấn đề với thư viện lớp .NET Core có thêm IsEmptyphương thức mở rộng. github.com/dotnet/corefx/issues/35054 Vui lòng kiểm tra và bình chọn nếu bạn thích và đồng ý.
RyotaMurohoshi

Câu trả lời:


100

Bạn có thể làm điều này:

public static Boolean IsEmpty<T>(this IEnumerable<T> source)
{
    if (source == null)
        return true; // or throw an exception
    return !source.Any();
}

Chỉnh sửa : Lưu ý rằng chỉ cần sử dụng phương thức .Count sẽ nhanh nếu nguồn bên dưới thực sự có thuộc tính Đếm nhanh. Một tối ưu hóa hợp lệ ở trên sẽ là phát hiện một vài loại cơ sở và chỉ cần sử dụng thuộc tính .Count của những loại đó, thay vì phương pháp .Any (), nhưng sau đó quay trở lại .Any () nếu không thể đảm bảo.


4
Hay sử dụng một dòng và trả về (source == null)? true:! source.Any (); (Nếu bạn không ném ra một ngoại lệ)
Gage

1
Tôi sẽ nói, có, ném một ngoại lệ cho null, nhưng sau đó thêm một phương thức mở rộng thứ hai được gọi IsNullOrEmpty().
devuxer

1
public static Boolean IsNullOrEmpty <T> (nguồn IEnumerable <T> này) {return source == null || ! source.Any (); }
dan

1
@Gage Now Today:return !source?.Any() ?? true;
ricksmt

@ricksmt Cảm ơn bạn đã cập nhật! Tôi chắc chắn sẽ sử dụng nó!
Gage

14

Tôi sẽ thực hiện một bổ sung nhỏ cho mã mà bạn dường như đã giải quyết: cũng kiểm tra ICollection, vì điều này cũng được thực hiện ngay cả bởi một số lớp chung không lỗi thời (tức là Queue<T>Stack<T>). Tôi cũng sẽ sử dụng asthay vì isnó dễ thành ngữ hơn và đã được chứng minh là nhanh hơn .

public static bool IsEmpty<T>(this IEnumerable<T> list)
{
    if (list == null)
    {
        throw new ArgumentNullException("list");
    }

    var genericCollection = list as ICollection<T>;
    if (genericCollection != null)
    {
        return genericCollection.Count == 0;
    }

    var nonGenericCollection = list as ICollection;
    if (nonGenericCollection != null)
    {
        return nonGenericCollection.Count == 0;
    }

    return !list.Any();
}

1
Tôi thích câu trả lời này. Một lời cảnh báo là một số bộ sưu tập sẽ ném ra các ngoại lệ khi chúng không triển khai đầy đủ một giao diện chẳng hạn như NotSupportedExceptionhoặc NotImplementedException. Lần đầu tiên tôi sử dụng ví dụ mã của bạn khi tôi phát hiện ra một bộ sưu tập mà tôi đang sử dụng đã ném một ngoại lệ cho Count (ai biết ...).
Sam

1
Tôi hiểu tại sao tối ưu hóa như vậy lại hữu ích cho các phương thức như Count () cần liệt kê tất cả các phần tử. Nhưng Any () chỉ cần liệt kê nhiều nhất một phần tử, vì vậy tôi không thấy điểm ở đây. Mặt khác, phôi và câu lệnh if bạn đang thêm là chi phí cố định bạn phải trả cho mỗi cuộc gọi sau đó.
codymanix

8

Bản thân LINQ phải thực hiện một số tối ưu hóa nghiêm trọng xung quanh phương thức Count () bằng cách nào đó.

Điều này có làm bạn ngạc nhiên không? Tôi tưởng tượng rằng đối với các IListtriển khai, Countchỉ cần đọc trực tiếp số lượng phần tử trong khi Anyphải truy vấn IEnumerable.GetEnumeratorphương thức, tạo một thể hiện và gọi MoveNextít nhất một lần.

/ CHỈNH SỬA @Matt:

Tôi chỉ có thể giả định rằng phương thức mở rộng Count () cho IEnumerable đang làm điều gì đó như sau:

Vâng, tất nhiên là có. Đây là những gì tôi muốn nói. Trên thực tế, nó sử dụng ICollectionthay vì IListnhưng kết quả là như nhau.


6

Tôi vừa viết một bài kiểm tra nhanh, hãy thử điều này:

 IEnumerable<Object> myList = new List<Object>();

 Stopwatch watch = new Stopwatch();

 int x;

 watch.Start();
 for (var i = 0; i <= 1000000; i++)
 {
    if (myList.Count() == 0) x = i; 
 }
 watch.Stop();

 Stopwatch watch2 = new Stopwatch();

 watch2.Start();
 for (var i = 0; i <= 1000000; i++)
 {
     if (!myList.Any()) x = i;
 }
 watch2.Stop();

 Console.WriteLine("myList.Count() = " + watch.ElapsedMilliseconds.ToString());
 Console.WriteLine("myList.Any() = " + watch2.ElapsedMilliseconds.ToString());
 Console.ReadLine();

Thứ hai chậm hơn gần ba lần :)

Thử lại kiểm tra đồng hồ bấm giờ với Ngăn xếp hoặc mảng hoặc các tình huống khác, nó thực sự phụ thuộc vào loại danh sách có vẻ như - bởi vì chúng chứng minh rằng Đếm chậm hơn.

Vì vậy, tôi đoán nó phụ thuộc vào loại danh sách bạn đang sử dụng!

(Chỉ cần lưu ý, tôi đưa hơn 2000 đối tượng vào Danh sách và đếm vẫn nhanh hơn, ngược lại với các loại khác)


12
Enumerable.Count<T>()có xử lý đặc biệt cho ICollection<T>. Nếu bạn thử điều này với một cái gì đó khác với danh sách cơ bản, tôi hy vọng bạn sẽ thấy kết quả khác (chậm hơn) đáng kể . Any()sẽ vẫn như cũ, mặc dù.
Marc Gravell

2
Tôi phải đồng tình với Marc; đây không phải là một bài kiểm tra thực sự công bằng.
Dan Tao

Bất kỳ ý tưởng tại sao không có xử lý đặc biệt Enumerable.Any<T>()cho ICollection<T>? chắc chắn rằng Any()không có tham số cũng có thể kiểm tra thuộc Counttính ICollection<T>?
Lukazoid

5

List.Countlà O (1) theo tài liệu của Microsoft:
http://msdn.microsoft.com/en-us/library/27b47ht3.aspx

vì vậy chỉ cần sử dụng List.Count == 0 nó nhanh hơn nhiều so với một truy vấn

Điều này là do nó có một thành viên dữ liệu được gọi là Count được cập nhật bất cứ khi nào một thứ gì đó được thêm vào hoặc xóa khỏi danh sách, vì vậy khi bạn gọi List.Countnó không phải lặp qua mọi phần tử để lấy nó, nó chỉ trả về thành viên dữ liệu.


1
nếu nó là "IEnumerable" thì không. (đối với người mới bắt đầu, IEnumerable không có thuộc tính "Count", nó có phương thức Count ().). Việc gọi "Count ()" sẽ yêu cầu IEnumerable kiểm tra từng phần tử trong danh sách. Trong khi "Bất kỳ" sẽ chỉ trả về ngay sau khi nó tìm thấy 1 phần tử.
00jt

Nó phụ thuộc vào nguồn dữ liệu. Nếu bạn sử dụng lợi suất để xây dựng IEnumerable, nó sẽ phải duyệt IEnumerable để biết kích thước của nó. Vì vậy, nó chỉ là O (1) trong một số trường hợp. Nó không phải lúc nào cũng là O (1).
TamusJRoyce

3

Tùy chọn thứ hai nhanh hơn nhiều nếu bạn có nhiều mục.

  • Any() trả lại ngay sau khi 1 mặt hàng được tìm thấy.
  • Count() phải tiếp tục xem qua toàn bộ danh sách.

Ví dụ, giả sử kiểu liệt kê có 1000 mục.

  • Any() sẽ kiểm tra cái đầu tiên, sau đó trả về true.
  • Count() sẽ trả về 1000 sau khi duyệt qua toàn bộ bảng liệt kê.

Điều này có thể tồi tệ hơn nếu bạn sử dụng một trong các ghi đè vị từ - Count () vẫn phải kiểm tra từng mục, ngay cả khi chỉ có một mục trùng khớp.

Bạn đã quen với việc sử dụng Any one - nó có ý nghĩa và có thể đọc được.

Một lưu ý - nếu bạn có một Danh sách, thay vì chỉ một IEnumerable thì hãy sử dụng thuộc tính Count của danh sách đó.


Sự khác biệt giữa Any () và Count () có vẻ rõ ràng, nhưng mã cấu hình của @ crossible dường như chỉ ra rằng Count () nhanh hơn đối với các triển khai nhất định của IEnumerable <T>. Đối với List <T>, tôi không thể lấy Any () cho kết quả nhanh hơn Count () cho đến khi kích thước danh sách lên đến hàng nghìn mục. Bản thân LINQ phải thực hiện một số tối ưu hóa nghiêm túc xung quanh phương thức Count () bằng cách nào đó.
Matt Hamilton

3

@Konrad điều khiến tôi ngạc nhiên là trong các thử nghiệm của mình, tôi đang chuyển danh sách vào một phương thức chấp nhận IEnumerable<T>, vì vậy thời gian chạy không thể tối ưu hóa nó bằng cách gọi phương thức mở rộng Count () cho IList<T>.

Tôi chỉ có thể giả định rằng phương thức mở rộng Count () cho IEnumerable đang làm điều gì đó như sau:

public static int Count<T>(this IEnumerable<T> list)
{
    if (list is IList<T>) return ((IList<T>)list).Count;

    int i = 0;
    foreach (var t in list) i++;
    return i;
}

... nói cách khác, một chút tối ưu hóa thời gian chạy cho trường hợp đặc biệt của IList<T> .

/ CHỈNH SỬA @Konrad +1 người bạn đời - bạn nói đúng về nó có nhiều khả năng được bật hơn ICollection<T>.


1

Ok, vậy cái này thì sao?

public static bool IsEmpty<T>(this IEnumerable<T> enumerable)
{
    return !enumerable.GetEnumerator().MoveNext();
}

CHỈNH SỬA: Tôi vừa nhận ra rằng ai đó đã phác thảo giải pháp này. Người ta đã đề cập rằng phương thức Any () sẽ thực hiện điều này, nhưng tại sao bạn không tự mình làm điều đó? Trân trọng


3
NHƯNG nó trở nên ít ngắn gọn hơn khi bạn đặt nó trong một usingkhối đúng cách vì nếu không thì bạn đã xây dựng một IDisposableđối tượng và sau đó bỏ nó đi. Sau đó, tất nhiên, nó trở nên ngắn gọn hơn khi bạn sử dụng phương thức mở rộng đã có và chỉ cần thay đổi nó thành return !enumerable.Any()(chính xác là điều này).
Dan Tao

Tại sao phải viết lại một phương pháp đã tồn tại? Như đã đề cập Any()thực hiện chính xác điều đó, vì vậy việc thêm chính xác cùng một phương thức với một tên khác sẽ chỉ gây nhầm lẫn.
Julien N

1

Một ý tưởng khác:

if(enumerable.FirstOrDefault() != null)

Tuy nhiên, tôi thích cách tiếp cận Any () hơn.


3
Điều gì sẽ xảy ra nếu bạn có một danh sách không trống trong đó phần tử đầu tiên là null?
Ekevoo

1

Điều này rất quan trọng để làm cho nó hoạt động với Entity Framework:

var genericCollection = list as ICollection<T>;

if (genericCollection != null)
{
   //your code 
}

Làm thế nào để trả lời câu hỏi? tập hợp không thể rỗng trong khi không có phần tử nào bên trong nó.
Martin Verjans

0

Nếu tôi kiểm tra với Count () Linq thực thi "SELECT COUNT (*) .." trong cơ sở dữ liệu, nhưng tôi cần kiểm tra xem kết quả có chứa dữ liệu hay không, tôi đã giải quyết việc giới thiệu FirstOrDefault () thay vì Count ();

Trước

var cfop = from tabelaCFOPs in ERPDAOManager.GetTable<TabelaCFOPs>()

if (cfop.Count() > 0)
{
    var itemCfop = cfop.First();
    //....
}

Sau

var cfop = from tabelaCFOPs in ERPDAOManager.GetTable<TabelaCFOPs>()

var itemCfop = cfop.FirstOrDefault();

if (itemCfop != null)
{
    //....
}

0
private bool NullTest<T>(T[] list, string attribute)

    {
        bool status = false;
        if (list != null)
        {
            int flag = 0;
            var property = GetProperty(list.FirstOrDefault(), attribute);
            foreach (T obj in list)
            {
                if (property.GetValue(obj, null) == null)
                    flag++;
            }
            status = flag == 0 ? true : false;
        }
        return status;
    }


public PropertyInfo GetProperty<T>(T obj, string str)

    {
        Expression<Func<T, string, PropertyInfo>> GetProperty = (TypeObj, Column) => TypeObj.GetType().GetProperty(TypeObj
            .GetType().GetProperties().ToList()
            .Find(property => property.Name
            .ToLower() == Column
            .ToLower()).Name.ToString());
        return GetProperty.Compile()(obj, str);
    }

0

Đây là cách tôi triển khai câu trả lời của Dan Tao, cho phép một vị ngữ:

public static bool IsEmpty<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
    if (source == null) throw new ArgumentNullException();
    if (IsCollectionAndEmpty(source)) return true;
    return !source.Any(predicate);
}

public static bool IsEmpty<TSource>(this IEnumerable<TSource> source)
{
    if (source == null) throw new ArgumentNullException();
    if (IsCollectionAndEmpty(source)) return true;
    return !source.Any();
}

private static bool IsCollectionAndEmpty<TSource>(IEnumerable<TSource> source)
{
    var genericCollection = source as ICollection<TSource>;
    if (genericCollection != null) return genericCollection.Count == 0;
    var nonGenericCollection = source as ICollection;
    if (nonGenericCollection != null) return nonGenericCollection.Count == 0;
    return false;
}

-1
List<T> li = new List<T>();
(li.First().DefaultValue.HasValue) ? string.Format("{0:yyyy/MM/dd}", sender.First().DefaultValue.Value) : string.Empty;

-3

myList.ToList().Count == 0. Đó là tất cả


1
Đây là một ý tưởng khủng khiếp. Không nên lạm dụng ToList () vì nó buộc có thể đánh giá đầy đủ. Sử dụng .Any () để thay thế.
Jon Rea

-5

Phương pháp mở rộng này phù hợp với tôi:

public static bool IsEmpty<T>(this IEnumerable<T> enumerable)
{
    try
    {
        enumerable.First();
        return false;
    }
    catch (InvalidOperationException)
    {
        return true;
    }
}

5
Tránh sử dụng các ngoại lệ như vậy. Trong đoạn mã trên, bạn mong đợi một ngoại lệ cho các đầu vào nhất định, được xác định rõ ràng (tức là các liệt kê trống). Do đó, chúng không phải là ngoại lệ, chúng là quy luật. Đó là sự lạm dụng cơ chế kiểm soát này có ảnh hưởng đến khả năng đọc và hiệu suất. Dành việc sử dụng các ngoại lệ cho các trường hợp thực sự đặc biệt.
Konrad Rudolph

Nói chung, tôi sẽ đồng ý. Nhưng đây là một cách giải quyết cho một phương thức IsEmpty bị thiếu tương ứng. Và tôi sẽ tranh luận rằng một cách giải quyết không bao giờ là cách lý tưởng để làm điều gì đó ... Hơn nữa, đặc biệt là trong trường hợp này, mục đích rất rõ ràng và mã "bẩn" được đóng gói và ẩn đi ở một nơi được xác định rõ ràng.
Jonny Dee

3
-1: Nếu bạn muốn làm theo cách này, hãy sử dụng FirstOrDefault (), như trong câu trả lời của ChulioMartinez.
Daniel Rose

3
Xử lý ngoại lệ có hiệu suất thực sự kém. Vì vậy, đây có thể là giải pháp tồi tệ nhất ở đây.
Julien N

"Các trường hợp ngoại lệ nên đặc biệt." - không sử dụng chúng cho dòng chương trình bình thường.
Jon Rea
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.