ตรวจสอบว่ารายการว่างเปล่าด้วย LINQ


122

วิธีใด "ดีที่สุด" (โดยคำนึงถึงความเร็วและความสามารถในการอ่าน) ในการพิจารณาว่ารายการว่างหรือไม่ แม้ว่ารายการจะเป็นประเภทIEnumerable<T>และไม่มีคุณสมบัติ Count ก็ตาม

ตอนนี้ฉันกำลังโยนระหว่างสิ่งนี้:

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

และนี่:

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

ฉันเดาว่าตัวเลือกที่สองเร็วกว่าเนื่องจากจะกลับมาพร้อมผลลัพธ์ทันทีที่เห็นรายการแรกในขณะที่ตัวเลือกที่สอง (สำหรับ IEnumerable) จะต้องไปที่ทุกรายการเพื่อส่งคืนการนับ

ดังที่กล่าวไว้ตัวเลือกที่สองดูเหมือนว่าคุณสามารถอ่านได้หรือไม่? คุณต้องการอะไร หรือคุณสามารถคิดวิธีที่ดีกว่าในการทดสอบรายการว่างได้หรือไม่?

การตอบสนองของEdit @ lassevk ดูเหมือนจะมีเหตุผลมากที่สุดควบคู่ไปกับการตรวจสอบรันไทม์เล็กน้อยเพื่อใช้การนับแคชหากเป็นไปได้ดังนี้:

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

    return !list.Any();
}

5
มากขึ้นดีกว่าไม่ผสมisและcastแต่การใช้asและการnullตรวจสอบ:ICollection<T> collection = list as ICollection<T>; if (collection != null) return colllection.Count;
abatishchev

2
ทำไมต้องเขียนวิธีพิเศษ? ไม่list.Any()เทียบเท่ากับlist.IsEmpty? วิธีการเฟรมเวิร์กควรได้รับการปรับให้เหมาะสม - ควรเขียนใหม่ก็ต่อเมื่อคุณคิดว่ามันเป็นคอขวดที่สมบูรณ์แบบ
dbkk

6
มีใครสนใจที่จะวัดประสิทธิภาพจากการใช้งานที่แนะนำหรือทุกคนแค่ทิ้งความคิด?
Michael Brown

ฉันแนะนำปัญหาให้กับไลบรารีคลาส. NET Core ที่เพิ่มIsEmptyวิธีการขยาย github.com/dotnet/corefx/issues/35054 โปรดตรวจสอบและลงคะแนนหากคุณชอบและเห็นด้วย
RyotaMurohoshi

คำตอบ:


100

คุณสามารถทำได้:

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

แก้ไข : โปรดทราบว่าการใช้เมธอด. นับจะรวดเร็วหากแหล่งที่มาที่แท้จริงมีคุณสมบัติการนับที่รวดเร็ว การปรับให้เหมาะสมที่ถูกต้องข้างต้นจะตรวจพบประเภทฐานสองสามประเภทและใช้คุณสมบัติ. นับของสิ่งเหล่านั้นแทนวิธี. () ใด ๆ แต่จะถอยกลับไปที่. Any () หากไม่สามารถรับประกันได้


4
หรือใช้บรรทัดเดียวแล้วส่งคืน (source == null)? จริง:! source.Any (); (ถ้าคุณไม่โยนข้อยกเว้น)
Gage

1
ผมจะบอกว่าใช่โยนข้อยกเว้นสำหรับ null IsNullOrEmpty()แต่แล้วเพิ่มวิธีขยายเป็นครั้งที่สองที่เรียกว่า
devuxer

1
สาธารณะคงบูลีน IsNullOrEmpty <T> (แหล่งที่มา <T> ของ IEnumerable นี้) {return source == null || ! source.Any (); }
แดน

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

@ricksmt ขอบคุณสำหรับการอัพเดท! ฉันจะใช้มันอย่างแน่นอน!
Gage

14

ฉันจะเพิ่มรหัสเล็กน้อยที่คุณดูเหมือนจะตัดสิน: ตรวจสอบด้วยICollectionเนื่องจากสิ่งนี้ถูกนำไปใช้แม้กระทั่งในคลาสทั่วไปที่ไม่ล้าสมัยเช่นกัน (เช่นQueue<T>และStack<T>) ฉันยังอยากจะใช้asแทนisเป็นมันสำนวนมากขึ้นและได้รับการแสดงที่จะได้เร็วขึ้น

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
ฉันชอบคำตอบนี้ หนึ่งคำเตือนที่ว่าคอลเลกชันบางส่วนจะโยนข้อยกเว้นเมื่อพวกเขาไม่ได้อย่างเต็มที่ใช้อินเตอร์เฟซเช่นหรือNotSupportedException NotImplementedExceptionฉันใช้ตัวอย่างโค้ดของคุณเป็นครั้งแรกเมื่อฉันพบคอลเล็กชันที่ฉันใช้โยนข้อยกเว้นสำหรับ Count (ใครจะรู้ ... )
แซม

1
ฉันเข้าใจว่าทำไมการเพิ่มประสิทธิภาพดังกล่าวจึงมีประโยชน์สำหรับเมธอดเช่น Count () ซึ่งจำเป็นต้องระบุองค์ประกอบทั้งหมด แต่ Any () ต้องการเพียงแค่ระบุองค์ประกอบมากที่สุดเท่านั้นดังนั้นฉันจึงไม่เห็นประเด็นตรงนี้ ในทางกลับกันการร่ายและ if-statement ที่คุณเพิ่มเป็นต้นทุนคงที่ที่คุณต้องจ่ายสำหรับการโทรทุกครั้ง
codymanix

8

LINQ เองต้องทำการเพิ่มประสิทธิภาพอย่างจริงจังเกี่ยวกับวิธีการ Count () อย่างใด

สิ่งนี้ทำให้คุณประหลาดใจหรือไม่? ฉันคิดว่าสำหรับIListการใช้งานCountเพียงแค่อ่านจำนวนองค์ประกอบโดยตรงในขณะที่Anyต้องค้นหาIEnumerable.GetEnumeratorวิธีการสร้างอินสแตนซ์และโทรMoveNextอย่างน้อยหนึ่งครั้ง

/ แก้ไข @ แมท:

ฉันสามารถสันนิษฐานได้ว่าวิธีการขยาย Count () สำหรับ IEnumerable กำลังทำสิ่งนี้:

ใช่แน่นอน นี่คือสิ่งที่ฉันหมายถึง จริงๆแล้วใช้ICollectionแทนIListแต่ผลลัพธ์เหมือนกัน


6

ฉันเพิ่งเขียนการทดสอบอย่างรวดเร็วลองสิ่งนี้:

 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();

ครั้งที่สองช้ากว่าเกือบสามเท่า :)

ลองทดสอบนาฬิกาจับเวลาอีกครั้งด้วย Stack หรือ array หรือสถานการณ์อื่น ๆ จริงๆแล้วมันขึ้นอยู่กับประเภทของรายการ - เพราะพวกเขาพิสูจน์ว่า Count ช้ากว่า

ฉันเดาว่ามันขึ้นอยู่กับประเภทของรายการที่คุณใช้!

(เพื่อชี้ให้เห็นว่าฉันใส่วัตถุมากกว่า 2,000 รายการในรายการและการนับก็ยังเร็วกว่าตรงกันข้ามกับประเภทอื่น ๆ )


12
Enumerable.Count<T>()ICollection<T>มีการจัดการพิเศษสำหรับ หากคุณลองใช้สิ่งอื่นที่ไม่ใช่รายการพื้นฐานฉันคาดว่าคุณจะเห็นผลลัพธ์ที่แตกต่างอย่างมาก (ช้ากว่า) Any()จะยังคงเหมือนเดิมแม้ว่า
Marc Gravell

2
ฉันต้องเห็นด้วยกับมาร์ค; นี่ไม่ใช่การทดสอบที่ยุติธรรมจริงๆ
ด่านเต๋า

มีความคิดว่าทำไมไม่มีการจัดการพิเศษEnumerable.Any<T>()สำหรับICollection<T>? แน่นอนว่าไม่มีพารามิเตอร์Any()ก็สามารถตรวจสอบCountคุณสมบัติได้ICollection<T>เช่นกัน?
Lukazoid

5

List.Countเป็น O (1) ตามเอกสารของ Microsoft:
http://msdn.microsoft.com/en-us/library/27b47ht3.aspx

ดังนั้นเพียงแค่ใช้List.Count == 0มันเร็วกว่าแบบสอบถามมาก

เนื่องจากมีสมาชิกข้อมูลที่เรียกว่า Count ซึ่งมีการอัปเดตทุกครั้งที่มีการเพิ่มหรือลบบางสิ่งออกจากรายการดังนั้นเมื่อคุณเรียกList.Countมันไม่จำเป็นต้องวนซ้ำทุกองค์ประกอบเพื่อรับมันก็จะส่งคืนสมาชิกข้อมูล


1
ถ้าเป็น "IEnumerable" ก็ไม่ (สำหรับผู้เริ่ม IEnumerable ไม่มีคุณสมบัติ "Count" แต่ก็มีวิธี Count ()) การเรียก "Count ()" จะต้องให้ IEnumerable ตรวจสอบทุกองค์ประกอบในรายการ ในขณะที่ "ใด ๆ " จะกลับมาทันทีที่พบ 1 องค์ประกอบ
00jt

ขึ้นอยู่กับแหล่งข้อมูล หากคุณใช้ yield เพื่อสร้าง IEnumerable จะต้องสำรวจ IEnumerable เพื่อให้ทราบขนาด ดังนั้นจึงเป็นเพียง O (1) ในบางกรณี ไม่ใช่ O (1) เสมอไป
TamusJRoyce

3

ตัวเลือกที่สองจะเร็วกว่ามากหากคุณมีหลายรายการ

  • Any() คืนทันทีที่พบ 1 รายการ
  • Count() ต้องดำเนินการต่อในรายการทั้งหมด

ตัวอย่างเช่นสมมติว่าการแจงนับมี 1,000 รายการ

  • Any() จะตรวจสอบรายการแรกจากนั้นคืนค่าจริง
  • Count() จะคืนค่า 1,000 หลังจากข้ามการแจงนับทั้งหมด

สิ่งนี้อาจแย่ลงหากคุณใช้หนึ่งในการแทนที่เพรดิเคต - Count () ยังคงต้องตรวจสอบทุกรายการแม้ว่าจะมีเพียงรายการเดียวก็ตาม

คุณเคยชินกับการใช้อันใดอันหนึ่ง - มันสมเหตุสมผลและอ่านได้

ข้อแม้ประการหนึ่ง - หากคุณมีรายการแทนที่จะเป็นเพียง IEnumerable ให้ใช้คุณสมบัติ Count ของรายการนั้น


ความแตกต่างระหว่าง Any () และ Count () ดูเหมือนชัดเจน แต่โค้ดการทำโปรไฟล์ของ @ crucible ดูเหมือนจะระบุว่า Count () เร็วกว่าสำหรับการใช้งาน IEnumerable <T> บางอย่าง สำหรับรายการ <T> ฉันไม่สามารถรับ Any () เพื่อให้ได้ผลลัพธ์เร็วกว่า Count () จนกว่าขนาดรายการจะเพิ่มขึ้นเป็นพันรายการ LINQ เองต้องทำการเพิ่มประสิทธิภาพอย่างจริงจังเกี่ยวกับวิธีการ Count () อย่างใด
Matt Hamilton

3

@ Konrad สิ่งที่ทำให้ฉันประหลาดใจคือในการทดสอบของฉันฉันกำลังส่งรายการไปยังวิธีการที่ยอมรับIEnumerable<T>ดังนั้นรันไทม์จึงไม่สามารถปรับให้เหมาะสมได้โดยเรียกวิธีการขยาย Count () สำหรับIList<T>วิธีส่วนขยายสำหรับ

ฉันสามารถสันนิษฐานได้ว่าวิธีการขยาย Count () สำหรับ IEnumerable กำลังทำสิ่งนี้:

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;
}

... กล่าวอีกนัยหนึ่งคือการเพิ่มประสิทธิภาพรันไทม์เล็กน้อยสำหรับกรณีพิเศษของIList<T>.

/ แก้ไข @Konrad 1 คู่ - ICollection<T>คุณสิทธิกำลังมันมีโอกาสมากขึ้นในการเป็น


1

ตกลงแล้วอันนี้ล่ะ?

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

แก้ไข: ฉันเพิ่งรู้ว่ามีคนร่างโซลูชันนี้แล้ว มีการกล่าวถึงวิธี Any () จะทำสิ่งนี้ แต่ทำไมไม่ทำเอง? ความนับถือ


3
แต่มันจะสั้นลงเมื่อคุณใส่ไว้ในusingบล็อกอย่างถูกต้องเนื่องจากไม่เช่นนั้นคุณได้สร้างIDisposableออบเจ็กต์แล้วละทิ้งไป จากนั้นแน่นอนว่ามันจะรวบรัดมากขึ้นเมื่อคุณใช้วิธีการขยายที่มีอยู่แล้วและเปลี่ยนเป็นreturn !enumerable.Any()(ซึ่งทำได้อย่างแม่นยำ)
ด่านเต๋า

เหตุใดจึงต้องเขียนวิธีการที่มีอยู่แล้วใหม่ ดังที่ได้กล่าวไว้จะAny()ดำเนินการอย่างนั้นดังนั้นการเพิ่มวิธีการเดียวกันกับชื่ออื่นจะทำให้เกิดความสับสน
Julien N

1

แนวคิดอื่น:

if(enumerable.FirstOrDefault() != null)

อย่างไรก็ตามฉันชอบวิธี Any () มากกว่า


3
จะเกิดอะไรขึ้นถ้าคุณมีรายการที่ไม่ว่างซึ่งองค์ประกอบแรกเป็นโมฆะ
Ekevoo

1

นี่เป็นสิ่งสำคัญที่จะทำให้สิ่งนี้ทำงานร่วมกับ Entity Framework:

var genericCollection = list as ICollection<T>;

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

วิธีนี้ตอบคำถาม? คอลเลกชันต้องไม่เป็นโมฆะในขณะที่ไม่มีองค์ประกอบอยู่ภายใน
Martin Verjans

0

ถ้าฉันตรวจสอบด้วย Count () Linq เรียกใช้ "SELECT COUNT (*) .. " ในฐานข้อมูล แต่ฉันต้องการตรวจสอบว่าผลลัพธ์มีข้อมูลหรือไม่ฉันตัดสินใจที่จะแนะนำ FirstOrDefault () แทน Count ();

ก่อน

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

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

หลังจาก

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

นี่คือการใช้คำตอบของ Dan Tao ของฉันโดยอนุญาตให้มีเพรดิเคต:

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;
}


-3

myList.ToList().Count == 0. นั่นคือทั้งหมด


1
นี่เป็นความคิดที่แย่มาก ไม่ควรใช้ ToList () มากเกินไปเนื่องจากจะบังคับให้ enumerable ได้รับการประเมินอย่างครบถ้วน ใช้. Any () แทน
Jon Rea

-5

วิธีการขยายนี้ใช้ได้กับฉัน:

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

5
หลีกเลี่ยงการใช้ข้อยกเว้นดังกล่าว ในโค้ดด้านบนคุณคาดว่าจะมีข้อยกเว้นสำหรับอินพุตที่กำหนดไว้อย่างดี (เช่นการแจงนับว่าง) ดังนั้นจึงไม่มีข้อยกเว้น แต่เป็นกฎ นั่นเป็นการใช้กลไกการควบคุมในทางที่ผิดซึ่งมีผลต่อความสามารถในการอ่านและประสิทธิภาพ สงวนการใช้ข้อยกเว้นสำหรับกรณีพิเศษอย่างแท้จริง
Konrad Rudolph

โดยทั่วไปฉันเห็นด้วย แต่นี่เป็นวิธีแก้ปัญหาสำหรับเมธอด IsEmpty ที่เกี่ยวข้องที่หายไป และฉันจะเถียงว่าวิธีแก้ปัญหาไม่เคยเป็นวิธีที่ดีที่สุดในการทำบางสิ่ง ... ยิ่งไปกว่านั้นโดยเฉพาะอย่างยิ่งในกรณีนี้เจตนานั้นชัดเจนมากและรหัส "สกปรก" จะถูกห่อหุ้มและซ่อนไว้ในที่ที่กำหนดไว้อย่างดี
จอนนี่ดี

3
-1: หากคุณต้องการทำในลักษณะนี้ให้ใช้ FirstOrDefault () เช่นเดียวกับคำตอบของ ChulioMartinez
Daniel Rose

3
การจัดการข้อยกเว้นมีประสิทธิภาพการทำงานที่แย่มาก นี่อาจเป็นทางออกที่แย่ที่สุดที่นี่
Julien N

"ข้อยกเว้นควรมีความพิเศษ" - อย่าใช้สำหรับโฟลว์โปรแกรมปกติ
จอนเรีย
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.