วิธีใดที่ทำงานได้ดีกว่า: .Any () vs .Count ()> 0


578

ในSystem.Linqnamespace ตอนนี้เราสามารถขยายของเราIEnumerable เป็นที่จะมีใด ๆ ()และนับ () วิธีการขยาย

ฉันได้รับแจ้งเมื่อไม่นานมานี้ว่าหากฉันต้องการตรวจสอบว่าคอลเล็กชันมี 1 หรือมากกว่าไอเท็มอยู่ข้างในฉันควรใช้.Any()วิธีการขยายแทนวิธีการ.Count() > 0ต่อเนื่องจากวิธีการ.Count()ขยายต้องวนซ้ำทุกรายการ

ประการที่สองคอลเลกชันบางรายที่มีคุณสมบัติ (ไม่ใช่วิธีขยาย) ที่เป็นหรือCount Lengthมันจะเป็นการดีกว่าถ้าจะใช้สิ่งเหล่านั้นแทน.Any()หรือ.Count()?

อือ?


ดีกว่าที่จะใช้ Any () กับ Enumerables และ Count on Collections หากมีคนรู้สึกว่าการเขียน '(somecollection.Count> 0)' จะทำให้เกิดความสับสนหรือทำให้เกิดปัญหาการอ่านได้ดีกว่าให้เขียนมันเป็นวิธีการขยายชื่อ Any () จากนั้นทุกคนก็พึงพอใจ ประสิทธิภาพการทำงานเช่นเดียวกับการอ่านได้ฉลาด เพื่อให้รหัสของคุณทั้งหมดจะมีความสอดคล้องและนักพัฒนารายบุคคลในโครงการของคุณไม่จำเป็นต้องกังวลเกี่ยวกับการเลือก Count vs Any
Mahesh Bongani

คำตอบ:


709

หากคุณเริ่มต้นกับสิ่งที่มี.Lengthหรือ.Count(เช่นICollection<T>, IList<T>, List<T>ฯลฯ ) - แล้วนี้จะเป็นตัวเลือกที่เร็วที่สุดเพราะมันไม่จำเป็นต้องไปผ่านGetEnumerator()/ MoveNext()/ Dispose()ลำดับที่จำเป็นโดยAny()การตรวจสอบไม่ว่างเปล่าIEnumerable<T>ลำดับ .

เพียงIEnumerable<T>แล้วAny()จะโดยทั่วไปจะเร็วเป็นเพียงมีลักษณะที่หนึ่งซ้ำ อย่างไรก็ตามโปรดทราบว่าการใช้งาน LINQ-to-Objects ของการCount()ตรวจสอบICollection<T>(ใช้.Countเป็นการเพิ่มประสิทธิภาพ) - ดังนั้นหากแหล่งข้อมูลพื้นฐานของคุณเป็นรายการ / คอลเลกชันโดยตรงจะไม่แตกต่างกันมาก อย่าถามฉันว่าทำไมมันไม่ใช้ non-generic ICollection...

แน่นอนถ้าคุณใช้ LINQ เพื่อกรองมัน ฯลฯ ( Whereฯลฯ ) คุณจะมีลำดับตามตัววนซ้ำบล็อกและดังนั้นการICollection<T>เพิ่มประสิทธิภาพนี้จะไร้ประโยชน์

โดยทั่วไปด้วยIEnumerable<T>: ติดกับAny();-p


9
Marc: ICollection <T> ไม่ได้มาจาก ICollection ฉันก็ประหลาดใจเหมือนกัน แต่ตัวสะท้อนแสงไม่ได้โกหก
ไบรอันวัตส์

7
ไม่มีการใช้งานใด ๆ () ตรวจสอบส่วนต่อประสาน ICollection และตรวจสอบคุณสมบัติ Count หรือไม่
derigel

313
ฉันคิดว่ามีเหตุผลอื่นที่ใช้ Any () เกือบตลอดเวลา มันส่งสัญญาณความตั้งใจที่ชัดเจนของนักพัฒนา หากคุณไม่สนใจที่จะรู้จำนวนของรายการ แต่ถ้ามีบางอย่างแล้ว somecollection.Any () นั้นง่ายและชัดเจนกว่า somecollection.Count> 0
TJKjaer

13
@huttelihut - กี่นักพัฒนาไม่คุณรู้ว่าใครกำลังสับสนอย่างแท้จริงโดยคำสั่ง(somecollection.Count > 0)? รหัสของเราทั้งหมดก่อนที่จะเปิดตัววิธีการใด ๆ ของ LINQ .Any () ยากที่จะเข้าใจหรือไม่?
CraigTP

25
@JLRishe - ฉันยังรู้สึกว่าsomeCollection.Count > 0ชัดเจนsomeCollection.Any()และมีประโยชน์เพิ่มเติมของประสิทธิภาพที่ดีขึ้นและไม่ต้องการ LINQ จริงอยู่นี่เป็นกรณีที่ง่ายมากและโครงสร้างอื่น ๆ ที่ใช้ตัวดำเนินการ LINQ จะสื่อให้ผู้พัฒนาตั้งใจชัดเจนกว่าตัวเลือกที่ไม่ใช่ LINQ
CraigTP

65

หมายเหตุ:ฉันเขียนคำตอบนี้เมื่อ Entity Framework 4 เป็นจริง ประเด็นของคำตอบนี้ไม่ได้เป็นการทดสอบ.Any()เทียบกับเรื่องเล็กน้อย .Count()ประเด็นก็คือการส่งสัญญาณว่า EF นั้นไม่สมบูรณ์แบบ เวอร์ชันที่ใหม่กว่านั้นดีกว่า ... แต่ถ้าคุณมีส่วนหนึ่งของรหัสที่ช้าและใช้ EF ทดสอบกับ TSQL โดยตรงและเปรียบเทียบประสิทธิภาพแทนที่จะพึ่งพาสมมติฐาน (นั่น.Any()คือเร็วกว่าเสมอ.Count() > 0)


ในขณะที่ฉันเห็นด้วยกับคำตอบและข้อคิดเห็นที่ได้รับการโหวตมากที่สุดโดยเฉพาะอย่างยิ่งในจุดAnyส่งสัญญาณความตั้งใจของนักพัฒนาที่ดีกว่าCount() > 0- ฉันเคยมีสถานการณ์ที่ Count เร็วขึ้นตามลำดับความสำคัญบน SQL Server (EntityFramework 4)

นี่คือข้อความค้นหาที่มีAnyข้อยกเว้นการหมดเวลาของ thew (ใน ~ 200,000 ระเบียน):

con = db.Contacts.
    Where(a => a.CompanyId == companyId && a.ContactStatusId <= (int) Const.ContactStatusEnum.Reactivated
        && !a.NewsletterLogs.Any(b => b.NewsletterLogTypeId == (int) Const.NewsletterLogTypeEnum.Unsubscr)
    ).OrderBy(a => a.ContactId).
    Skip(position - 1).
    Take(1).FirstOrDefault();

Count รุ่นที่ดำเนินการในเรื่องของมิลลิวินาที:

con = db.Contacts.
    Where(a => a.CompanyId == companyId && a.ContactStatusId <= (int) Const.ContactStatusEnum.Reactivated
        && a.NewsletterLogs.Count(b => b.NewsletterLogTypeId == (int) Const.NewsletterLogTypeEnum.Unsubscr) == 0
    ).OrderBy(a => a.ContactId).
    Skip(position - 1).
    Take(1).FirstOrDefault();

ฉันต้องการหาวิธีที่จะดูว่า SQL ที่แน่นอนทั้ง LINQs ผลิต - แต่มันชัดเจนว่ามีความแตกต่างอย่างมากระหว่างCountและAnyในบางกรณีและโชคไม่ดีที่ดูเหมือนว่าคุณไม่สามารถติดกับAnyทุกกรณี

แก้ไข: นี่คือ SQL ที่สร้างขึ้น ความงามที่คุณเห็น;)

ANY:

exec sp_executesql N'SELECT ด้านบน (1) 
[Project2]. [ContactId] AS [ContactId], 
[Project2]. [CompanyId] AS [CompanyId], 
[Project2]. [ContactName] AS [ContactName] 
[Project2]. [FullName] AS [FullName] 
[Project2]. [ContactStatusId] AS [ContactStatusId], 
[Project2]. [สร้าง] AS [สร้าง]
จาก (เลือก [Project2]. [ContactId] AS [ContactId], [Project2]. [CompanyId] AS [CompanyId], [Project2]. [ContactName] AS [ContactName], [Project2]. [FullName] AS [FullName] , [Project2]. [ContactStatusId] AS [ContactStatusId], [Project2]. [สร้าง] AS [สร้าง], row_number () ขึ้นไป (เรียงตาม [Project2]. [ContactId] ASC) AS [row_number]
    จาก (เลือก 
        [Extent1]. [ContactId] AS [ContactId], 
        [Extent1]. [CompanyId] AS [CompanyId], 
        [Extent1]. [ContactName] เป็น [ContactName], 
        [Extent1]. [FullName] AS [FullName] 
        [Extent1]. [ContactStatusId] AS [ContactStatusId], 
        [Extent1]. [สร้าง] AS [สร้าง]
        จาก [dbo] [ติดต่อ] AS [Extent1]
        WHERE ([Extent1]. [CompanyId] = @ p__linq__0) และ ([Extent1]. [ContactStatusId] <= 3) และ (ไม่ใช่ EXISTS (เลือก 
            1 AS [C1]
            จาก [dbo]. [NewsletterLog] AS [Extent2]
            WHERE ([Extent1]. [ContactId] = [Extent2]. [ContactId]) และ (6 = [Extent2]. [NewsletterLogTypeId])
        ))
    ) AS [โครงการ 2]
) AS [โครงการ 2]
WHERE [Project2]. [row_number]> 99
เรียงตาม [Project2]. [ContactId] ASC ', N' @ p__linq__0 int ', @ p__linq__0 = 4

COUNT:

exec sp_executesql N'SELECT ด้านบน (1) 
[Project2]. [ContactId] AS [ContactId], 
[Project2]. [CompanyId] AS [CompanyId], 
[Project2]. [ContactName] AS [ContactName] 
[Project2]. [FullName] AS [FullName] 
[Project2]. [ContactStatusId] AS [ContactStatusId], 
[Project2]. [สร้าง] AS [สร้าง]
จาก (เลือก [Project2]. [ContactId] AS [ContactId], [Project2]. [CompanyId] AS [CompanyId], [Project2]. [ContactName] AS [ContactName], [Project2]. [FullName] AS [FullName] , [Project2]. [ContactStatusId] AS [ContactStatusId], [Project2]. [สร้าง] AS [สร้าง], row_number () ขึ้นไป (เรียงตาม [Project2]. [ContactId] ASC) AS [row_number]
    จาก (เลือก 
        [Project1]. [ContactId] AS [ContactId], 
        [Project1]. [CompanyId] AS [CompanyId], 
        [Project1]. [ContactName] AS [ContactName] 
        [Project1]. [FullName] AS [FullName] 
        [Project1]. [ContactStatusId] AS [ContactStatusId], 
        [Project1]. [สร้าง] AS [สร้าง]
        จาก (เลือก 
            [Extent1]. [ContactId] AS [ContactId], 
            [Extent1]. [CompanyId] AS [CompanyId], 
            [Extent1]. [ContactName] เป็น [ContactName], 
            [Extent1]. [FullName] AS [FullName] 
            [Extent1]. [ContactStatusId] AS [ContactStatusId], 
            [Extent1]. [สร้าง] AS [สร้าง], 
            (เลือก 
                COUNT (1) AS [A1]
                จาก [dbo]. [NewsletterLog] AS [Extent2]
                WHERE ([Extent1]. [ContactId] = [Extent2]. [ContactId]) และ (6 = [Extent2]. [NewsletterLogTypeId])) AS [C1]
            จาก [dbo] [ติดต่อ] AS [Extent1]
        ) AS [โครงการ 1]
        WHERE ([Project1]. [CompanyId] = @ p__linq__0) และ ([Project1]. [ContactStatusId] <= 3) AND (0 = [Project1]. [C1])
    ) AS [โครงการ 2]
) AS [โครงการ 2]
WHERE [Project2]. [row_number]> 99
เรียงตาม [Project2]. [ContactId] ASC ', N' @ p__linq__0 int ', @ p__linq__0 = 4

ดูเหมือนว่าบริสุทธิ์โดยที่ EXISTS ทำงานได้แย่กว่าการคำนวณ Count จากนั้นทำ Where With Count == 0

แจ้งให้เราทราบหากพวกคุณเห็นข้อผิดพลาดในการค้นพบของฉัน สิ่งที่สามารถนำออกมาได้ทั้งหมดนี้โดยไม่คำนึงถึงการสนทนาใด ๆ กับการนับจำนวนคือ LINQ ที่ซับซ้อนมากขึ้นนั้นดีกว่าเมื่อเขียนใหม่ในรูปแบบ Stored Procedure;)


2
ชอบที่จะเห็นแผนการแบบสอบถามของ SQL ที่สร้างขึ้นโดยแต่ละแบบสอบถาม linq สำหรับแต่ละสถานการณ์
Pure.Krome

43
ตาม SQL ทั้งหมดที่ฉันสามารถพูดได้: แบบสอบถามทั้งสองดูน่ากลัว ฉันรู้ว่ามีเหตุผลที่ปกติฉันเขียน TSQL ของตัวเอง ...
Marc Gravell

ทุกคนจะต้องมองผ่านแถวทั้งหมดเหมือนที่นับ ตัวอย่างของคุณให้ผลลัพธ์ที่น่ากลัวเช่นนี้แปลกไปเล็กน้อยในกรณีที่เลวร้ายที่สุด! ควรจะช้ากว่า Count สักหน่อย ในกรณีของคุณฉันจะมองหาวิธีในการลดความซับซ้อนของการเลือกอาจแยกมันเป็นขั้นตอนหรือจัดลำดับเงื่อนไขใหม่ถ้าเป็นไปได้ แต่ประเด็นของคุณที่ว่ากฎใด ๆ ที่ดีกว่านับไม่ได้ถือไว้! ใด ๆ ที่ดีกว่านับเป็นสิ่งที่ดีมาก
Bent

25

เนื่องจากนี่เป็นหัวข้อที่ได้รับความนิยมและคำตอบต่างกันฉันจึงต้องมองปัญหาใหม่

การทดสอบ env: EF 6.1.3, SQL Server, ระเบียน 300k

รูปแบบตาราง :

class TestTable
{
    [Key]
    public int Id { get; set; }

    public string Name { get; set; }

    public string Surname { get; set; }
}

รหัสทดสอบ:

class Program
{
    static void Main()
    {
        using (var context = new TestContext())
        {
            context.Database.Log = Console.WriteLine;

            context.TestTables.Where(x => x.Surname.Contains("Surname")).Any(x => x.Id > 1000);
            context.TestTables.Where(x => x.Surname.Contains("Surname") && x.Name.Contains("Name")).Any(x => x.Id > 1000);
            context.TestTables.Where(x => x.Surname.Contains("Surname")).Count(x => x.Id > 1000);
            context.TestTables.Where(x => x.Surname.Contains("Surname") && x.Name.Contains("Name")).Count(x => x.Id > 1000);

            Console.ReadLine();
        }
    }
}

ผล:

ใด ๆ () ~ 3ms

นับ () ~ 230ms สำหรับข้อความค้นหาแรก ~ 400ms สำหรับวินาที

หมายเหตุ:

สำหรับกรณีของฉัน EF ไม่ได้สร้าง SQL อย่าง @Ben ที่กล่าวถึงในโพสต์


4
Count() > 0สำหรับการเปรียบเทียบที่เหมาะสมที่คุณควรทำ : D
Andrew

1
Andrew, Count ()> 0 จะไม่ทำงานแตกต่างจาก Count () ในการทดสอบนี้โดยเฉพาะ
CodeMonkeyForHire

11

แก้ไข:ได้รับการแก้ไขใน EF เวอร์ชัน 6.1.1 และคำตอบนี้ไม่เกิดขึ้นจริง

สำหรับ SQL Server และ EF4-6 Count () จะทำงานเร็วกว่า Any () ประมาณสองเท่า

เมื่อคุณเรียกใช้ Table.Any () มันจะสร้างสิ่งที่ต้องการ (การแจ้งเตือน: อย่าทำร้ายสมองที่พยายามเข้าใจ )

SELECT 
CASE WHEN ( EXISTS (SELECT 
    1 AS [C1]
    FROM [Table] AS [Extent1]
)) THEN cast(1 as bit) WHEN ( NOT EXISTS (SELECT 
    1 AS [C1]
    FROM [Table] AS [Extent2]
)) THEN cast(0 as bit) END AS [C1]
FROM  ( SELECT 1 AS X ) AS [SingleRowTable1]

ที่ต้องสแกน 2 แถวด้วยเงื่อนไขของคุณ

ฉันไม่ชอบเขียนCount() > 0เพราะซ่อนความตั้งใจของฉัน ฉันชอบใช้เพรดิเคตที่กำหนดเองสำหรับสิ่งนี้:

public static class QueryExtensions
{
    public static bool Exists<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate)
    {
        return source.Count(predicate) > 0;
    }
}

ฉันก็สังเกตเห็นสิ่งนี้เช่นกัน SQL ใด ๆ () ไม่สมเหตุสมผลเลย ฉันไม่แน่ใจว่าทำไมพวกเขาไม่ทำ: กรณีเมื่อ (EXISTS (sql)) แล้ว 1 ELSE 0 END ฉันไม่สามารถคิดได้ว่าทำไมพวกเขาต้องทำ EXISTS เพื่อส่งคืน 0
scott.korin

นี่เป็นเท็จ คุณพบแผนการสืบค้นที่ไม่ถูกต้องโดยบังเอิญ สิ่งนี้เกิดขึ้น อะไรก็ได้เกือบจะเร็วกว่า
usr

ฉันตรวจสอบ sql ที่สร้างขึ้นใน 6.1.3 พวกเขาแก้ไขมัน: เลือกกรณีเมื่อ (อยู่ (เลือก 1 เป็น [C1] จาก [dbo]. [ตารางทดสอบ] เป็น [Extent1] ที่ไหน [Extent1]. [Id]> 1,000) จากนั้นนักแสดง (1 เป็นบิต) นักแสดงอื่น ๆ (0 เป็นบิต) END AS [C1] จาก (เลือก 1 AS X) เป็น [SingleRowTable1]
Ben

6

ขึ้นอยู่กับว่าชุดข้อมูลมีขนาดใหญ่เพียงใดและความต้องการด้านประสิทธิภาพของคุณคืออะไร

ถ้ามันไม่มีอะไรมโหฬารใช้รูปแบบที่อ่านได้มากที่สุดซึ่งสำหรับตัวฉันเองก็เป็นแบบนี้เพราะมันสั้นกว่าและอ่านง่ายแทนที่จะเป็นสมการ


2

เกี่ยวกับจำนวน ()วิธีการถ้าIEnumarableเป็นICollectionแล้วเราไม่สามารถย้ำทั่วทุกรายการเพราะเราสามารถดึงจำนวนสาขาของICollectionถ้าIEnumerableไม่ได้เป็นICollectionเราย้ำต้องข้ามรายการทั้งหมดใช้ในขณะที่มีMoveNextจะดูที่ .NET Framework รหัสสินค้า:

public static int Count<TSource>(this IEnumerable<TSource> source)
{
    if (source == null) 
        throw Error.ArgumentNull("source");

    ICollection<TSource> collectionoft = source as ICollection<TSource>;
    if (collectionoft != null) 
        return collectionoft.Count;

    ICollection collection = source as ICollection;
    if (collection != null) 
        return collection.Count;

    int count = 0;
    using (IEnumerator<TSource> e = source.GetEnumerator())
    {
        checked
        {
            while (e.MoveNext()) count++;
        }
    }
    return count;
}

การอ้างอิง: แหล่งอ้างอิงที่นับได้


2

คุณสามารถทำการทดสอบอย่างง่าย ๆ เพื่อหาสิ่งนี้:

var query = //make any query here
var timeCount = new Stopwatch();
timeCount.Start();
if (query.Count > 0)
{
}
timeCount.Stop();
var testCount = timeCount.Elapsed;

var timeAny = new Stopwatch();
timeAny.Start();
if (query.Any())
{
}
timeAny.Stop();
var testAny = timeAny.Elapsed;

ตรวจสอบค่าของ testCount และ testAny


1
นี่คือการทดสอบด้วยรหัสของคุณสำหรับการนับคุณสมบัติเทียบกับใด ๆ () นับการชนะทรัพย์สินใด ๆ ที่เทียบกับใด ๆ () ด้วย + 2x - ลิงค์
Stanislav Prusac

1
เพื่อผลลัพธ์ที่ดีกว่าคุณสามารถทำการเปรียบเทียบ 1,000 ครั้ง (หรือมากกว่า) ช่วยเฉลี่ยผลลัพธ์และหลีกเลี่ยงการสุ่มใด ๆ
โรมัน

เมื่อคุณทำการทดสอบเช่นวิธีการที่กล่าวถึงข้างต้นคุณต้องพิจารณาปัจจัยอื่น ๆ อีกมากมายเช่นโหลดบนฐานข้อมูล / เครือข่ายของคุณวางแผนการแคชด้านฐานข้อมูล ฯลฯ เพื่อทำการทดสอบที่ถูกต้องคุณควรออกแบบสภาพแวดล้อมที่แยกและถูกต้องด้วย
Vahid Farahmandian

สำหรับการเปรียบเทียบที่ดีกว่าควรถูกCountแทนที่ด้วยเมธอด Count () vs .Any () ไม่ใช่คุณสมบัติ คุณต้องใช้เวลาในการวนซ้ำ
daremachine

0

หากคุณใช้ Entity Framework และมีตารางขนาดใหญ่ที่มีบันทึกจำนวนมากๆ () ใด ๆก็จะเร็วขึ้นมาก ฉันจำครั้งหนึ่งที่ฉันต้องการตรวจสอบเพื่อดูว่าตารางว่างเปล่าและมีแถวนับล้าน ใช้เวลา 20-30 วินาทีในการนับ ()> 0 ให้เสร็จสมบูรณ์ มันเป็นทันทีกับใด ๆ ()

ใด ๆ ()อาจเป็นการปรับปรุงประสิทธิภาพเนื่องจากอาจไม่จำเป็นต้องวนซ้ำคอลเลกชันเพื่อรับจำนวนสิ่งของ มันต้องตีหนึ่งของพวกเขา หรือสำหรับการพูด LINQ-to-Entities SQL ที่สร้างขึ้นจะเป็น IF EXISTS (... ) มากกว่า SELECT COUNT ... หรือแม้แต่ SELECT * ....

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