LINQ: ไม่ vs ไม่ได้ทั้งหมด


272

บ่อยครั้งที่ฉันต้องการตรวจสอบว่าค่าที่มีให้ตรงกับหนึ่งในรายการ (เช่นเมื่อตรวจสอบ):

if (!acceptedValues.Any(v => v == someValue))
{
    // exception logic
}

เมื่อเร็ว ๆ นี้ฉันสังเกตเห็น ReSharper ขอให้ฉันทำแบบสอบถามเหล่านี้ให้ง่ายขึ้น:

if (acceptedValues.All(v => v != someValue))
{
    // exception logic
}

เห็นได้ชัดว่ามันเหมือนกันในเชิงตรรกะบางทีอาจจะอ่านได้มากกว่าเล็กน้อย (ถ้าคุณทำคณิตศาสตร์มามาก) คำถามของฉันคือ: สิ่งนี้ส่งผลให้เกิดผลงานหรือไม่?

มันให้ความรู้สึกเหมือนว่ามันควรจะ (เช่น.Any()ฟังดูเหมือนมันจะลัดวงจรในขณะที่.All()ดูเหมือนว่ามันจะไม่ได้) แต่ฉันไม่มีอะไรจะพิสูจน์เรื่องนี้ ไม่มีใครมีความรู้ในเชิงลึกเกี่ยวกับคำถามที่ว่าจะแก้ไขเหมือนเดิมหรือไม่หรือ ReSharper ทำให้ฉันหลงทาง?


6
คุณได้ลองถอดรหัส Linq เพื่อดูว่ามันทำอะไรอยู่?
RQDQ

9
ในกรณีนี้ที่จริงผมจะไปกับถ้า (acceptedValues.Contains (someValue)!) แต่แน่นอนนี่ไม่ใช่คำถาม :)
csgero

2
@csgero ฉันเห็นด้วย ข้างต้นคือการทำให้เข้าใจง่าย (อาจจะง่ายกว่า) ของตรรกะจริง
ทำเครื่องหมาย

1
"มันรู้สึกเหมือนว่ามันควรจะ (เช่น. ใด ๆ ) ดูเหมือนว่ามันจะลัดวงจรในขณะที่. ทั้งหมด () ดูเหมือนว่ามันไม่ได้)" - ไม่ให้ใครก็ตามที่มีสัญชาติญาณเสียง นั่นคือความเท่าเทียมกันทางตรรกะที่คุณสังเกตเห็นว่าพวกเขามีความเท่าเทียมกันในการลัดวงจร ความคิดของสักครู่เผยให้เห็นว่าทุกคนสามารถลาออกได้ทันทีที่พบกรณีที่ไม่มีคุณสมบัติ
Jim Balter

3
ฉันไม่เห็นด้วยกับ ReSharper เกี่ยวกับเรื่องนี้ เขียนขบวนความคิดที่สมเหตุสมผล if (!sequence.Any(v => v == true))หากคุณต้องการที่จะโยนยกเว้นถ้าเป็นรายการที่ต้องมีขาดหายไป: if (sequence.All(v => v < 10))หากคุณต้องการที่จะดำเนินการเฉพาะในกรณีที่สอดรับทุกอย่างที่จะเปคบาง:
Timo

คำตอบ:


344

การดำเนินการAllตาม ILSpy (ในขณะที่ฉันไปและมองมากกว่า "ดีวิธีการที่ใช้งานได้เช่น ... " ฉันอาจจะทำอย่างไรถ้าเราพูดถึงทฤษฎีมากกว่าผลกระทบ)

public static bool All<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
    if (source == null)
    {
        throw Error.ArgumentNull("source");
    }
    if (predicate == null)
    {
        throw Error.ArgumentNull("predicate");
    }
    foreach (TSource current in source)
    {
        if (!predicate(current))
        {
            return false;
        }
    }
    return true;
}

การดำเนินการAnyตาม ILSpy:

public static bool Any<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
    if (source == null)
    {
        throw Error.ArgumentNull("source");
    }
    if (predicate == null)
    {
        throw Error.ArgumentNull("predicate");
    }
    foreach (TSource current in source)
    {
        if (predicate(current))
        {
            return true;
        }
    }
    return false;
}

แน่นอนว่าอาจมีความแตกต่างเล็กน้อยในการผลิต IL แต่ไม่ไม่มีไม่มี IL นั้นค่อนข้างเหมือนกัน แต่สำหรับการกลับที่ชัดเจนของการกลับมาจริงในการแข่งขันภาคเปรียบเทียบกับการกลับมาที่ผิดพลาดในภาคที่ไม่ตรงกัน

นี่เป็น linq-for-objects แน่นอน มีความเป็นไปได้ที่ผู้ให้บริการ linq รายอื่นจะปฏิบัติดีกว่าอีกคนหนึ่ง แต่ถ้าเป็นเช่นนั้นจะเป็นการสุ่มที่ดีกว่าคนอื่นที่มีการใช้งานที่เหมาะสมกว่า

มันจะดูเหมือนว่ากฎลงมา แต่เพียงผู้เดียวกับความรู้สึกคนที่ง่ายและสามารถอ่านได้มากกว่าif(determineSomethingTrue) if(!determineSomethingFalse)และในความเป็นธรรมฉันคิดว่าพวกเขามีจุดที่ฉันมักจะพบว่ามีif(!someTest)ความสับสน * เมื่อมีการทดสอบทางเลือกของการใช้คำฟุ่มเฟื่อยและความซับซ้อนที่เท่าเทียมกันซึ่งจะกลับมาจริงสำหรับเงื่อนไขที่เราต้องการ แต่โดยส่วนตัวแล้วฉันไม่พบสิ่งใดที่จะให้ประโยชน์แก่อีกทางเลือกหนึ่งของสองทางเลือกที่คุณให้และอาจจะเอนตัวไปทางอดีตเล็กน้อยหากคำกริยามีความซับซ้อนมากขึ้น

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


8
ฉันไม่แน่ใจว่าสิ่งที่ทำอยู่เบื้องหลังบรรทัด แต่สำหรับฉันอ่านได้มากขึ้นคือ: ถ้า (ไม่มี) กว่าถ้า (ทั้งหมดไม่เท่ากัน)
VikciaR

49
มีความแตกต่างใหญ่เมื่อการแจงนับของคุณไม่มีค่า 'ใด ๆ ' จะคืนค่า FALSE เสมอและ 'ทั้งหมด' จะคืนค่า TRUE เสมอ ดังนั้นการพูดมากกว่าหนึ่งคือความเท่าเทียมทางตรรกะของอีกฝ่ายนั้นไม่เป็นความจริงทั้งหมด!
Arnaud

44
@Arnaud Anyจะกลับมาfalseและด้วยเหตุนี้!Anyจะกลับมาtrueดังนั้นพวกเขาจึงเหมือนกัน
Jon Hanna

11
@Arnaud ไม่มีบุคคลใดที่แสดงความคิดเห็นว่าใครพูดว่าอะไรก็ตามและทั้งหมดนั้นเทียบเท่ากันในเชิงตรรกะ หรือกล่าวอีกนัยหนึ่งว่าทุกคนที่แสดงความคิดเห็นไม่ได้พูดว่าสิ่งใดและทั้งหมดมีเหตุผลเทียบเท่า ความเท่าเทียมกันอยู่ระหว่าง! ใด ๆ (ภาคแสดง) และทั้งหมด (! ภาคแสดง)
Jim Balter

7
@ MacsDickinson นั้นไม่แตกต่างกันเลยเพราะคุณไม่ได้เปรียบเทียบภาคแสดงต่าง ๆ การเทียบเท่ากับการ!test.Any(x => x.Key == 3 && x.Value == 1)ใช้Allนั้นคือtest.All(x => !(x.Key == 3 && x.Value == 1))(ซึ่งแน่นอนเทียบเท่าtest.All(x => x.Key != 3 || x.Value != 1))
Jon Hanna

55

คุณอาจพบว่าวิธีการขยายเหล่านี้ทำให้โค้ดของคุณอ่านง่ายขึ้น:

public static bool None<TSource>(this IEnumerable<TSource> source)
{
    return !source.Any();
}

public static bool None<TSource>(this IEnumerable<TSource> source, 
                                 Func<TSource, bool> predicate)
{
    return !source.Any(predicate);
}

ตอนนี้แทนที่จะเป็นต้นฉบับของคุณ

if (!acceptedValues.Any(v => v == someValue))
{
    // exception logic
}

คุณสามารถพูดได้

if (acceptedValues.None(v => v == someValue))
{
    // exception logic
}

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

2
ฉันมองหาไม่มีและไม่พบมัน มันอ่านได้มากขึ้น
Rhyous

ฉันต้องเพิ่มการตรวจสอบ null: return source == null || ! source.Any (กริยา);
Rhyous

27

ทั้งสองฝ่ายจะมีประสิทธิภาพการทำงานที่เหมือนกันเพราะทั้งสองหยุดการแจงนับหลังจากผลที่ได้รับการพิจารณา - Any()ที่รายการแรกที่ผ่านประเมินกริยาไปtrueและที่รายการแรกประเมินเพื่อวินิจฉัยAll()false


21

All การลัดวงจรของการแข่งขันนัดแรกดังนั้นจึงไม่ใช่ปัญหา

ความบอบบางอย่างหนึ่งก็คือ

 bool allEven = Enumerable.Empty<int>().All(i => i % 2 == 0); 

เป็นความจริง. รายการทั้งหมดในลำดับนั้นเท่ากัน

สำหรับข้อมูลเพิ่มเติมเกี่ยวกับวิธีการนี้เอกสารประกอบสำหรับEnumerable.All


12
ใช่ แต่bool allEven = !Enumerable.Empty<int>().Any(i => i % 2 != 0)ก็เป็นจริงเช่นกัน
Jon Hanna

1
@ จอนไม่มีความหมาย! = ทั้งหมด ดังนั้นความหมายคุณอาจไม่มีหรือทั้งหมด แต่ในกรณีของ. All () ไม่มีเป็นเพียงชุดย่อยของคอลเลกชันทั้งหมดที่ส่งกลับค่าจริงสำหรับทุกคน +1 สำหรับแอนโธนี
Rune FS

@RuneFS ฉันไม่ได้ติดตาม ความหมายและไม่มีเหตุผล "ไม่มีที่ใดที่ไม่เป็นความจริงว่า ... " ย่อมเหมือนกับ "ทั้งหมดที่เป็นความจริง" เช่น "ไม่มีโครงการที่ได้รับการยอมรับจาก บริษัท ของเรา" จะมีคำตอบเดียวกับ "ที่ทุกโครงการที่ยอมรับจาก บริษัท อื่น ๆ " ...
Jon Hanna

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

@ เมื่อคุณพูดถึงตรรกะและฉันกำลังพูดภาษาศาสตร์ สมองมนุษย์ไม่สามารถประมวลผลเชิงลบได้ (ก่อนที่มันจะประมวลผลเชิงบวก ณ จุดที่มันสามารถคัดค้านได้) ดังนั้นในแง่นั้นมันมีความแตกต่างระหว่างคนทั้งสอง นั่นไม่ได้ทำให้ตรรกะที่คุณเสนอไม่ถูกต้อง
Rune FS

8

All()กำหนดว่าองค์ประกอบทั้งหมดของลำดับเป็นไปตามเงื่อนไขหรือไม่
Any()กำหนดว่าองค์ประกอบใด ๆ ของลำดับเป็นไปตามเงื่อนไขหรือไม่

var numbers = new[]{1,2,3};

numbers.All(n => n % 2 == 0); // returns false
numbers.Any(n => n % 2 == 0); // returns true

7

ตามลิงค์นี้

ใด ๆ - ตรวจสอบการแข่งขันอย่างน้อยหนึ่งครั้ง

ทั้งหมด - ตรวจสอบว่าตรงกันทั้งหมด


1
คุณพูดถูก แต่พวกเขาหยุดในเวลาเดียวกันสำหรับคอลเล็กชันที่กำหนด ตัวแบ่งทั้งหมดเมื่อเงื่อนไขล้มเหลวและตัวแบ่งใด ๆ เมื่อตรงกับเพรดิเคตของคุณ ในทางเทคนิคแล้วไม่แตกต่างกันยกเว้นในกรณีที่เกิดเหตุ
WPFKK

6

เนื่องจากคำตอบอื่น ๆ ได้รับการคุ้มครองอย่างดี: นี่ไม่ได้เกี่ยวกับประสิทธิภาพ แต่เป็นเรื่องที่ชัดเจน

มีการสนับสนุนอย่างกว้างขวางสำหรับตัวเลือกทั้งสองของคุณ:

if (!acceptedValues.Any(v => v == someValue))
{
    // exception logic
}

if (acceptedValues.All(v => v != someValue))
{
    // exception logic
}

แต่ฉันคิดว่าสิ่งนี้อาจบรรลุการสนับสนุนที่กว้างขึ้น :

var isValueAccepted = acceptedValues.Any(v => v == someValue);
if (!isValueAccepted)
{
    // exception logic
}

เพียงแค่คำนวณบูลีน (และตั้งชื่อ) ก่อนที่จะลบล้างสิ่งใดก็ตามที่ทำให้สิ่งนี้อยู่ในใจฉันมากมาย


3

หากคุณดูที่แหล่งที่มานับคุณจะเห็นว่าการดำเนินการAnyและAllอยู่ใกล้มาก:

public static bool Any<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate) {
    if (source == null) throw Error.ArgumentNull("source");
    if (predicate == null) throw Error.ArgumentNull("predicate");
    foreach (TSource element in source) {
        if (predicate(element)) return true;
    }
    return false;
}

public static bool All<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate) {
    if (source == null) throw Error.ArgumentNull("source");
    if (predicate == null) throw Error.ArgumentNull("predicate");
    foreach (TSource element in source) {
        if (!predicate(element)) return false;
    }
    return true;
}

ไม่มีวิธีใดที่วิธีการหนึ่งจะเร็วกว่าวิธีอื่นอย่างมีนัยสำคัญเนื่องจากความแตกต่างเพียงอย่างเดียวคือการปฏิเสธแบบบูลีนดังนั้นจึงควรอ่านมากกว่าการชนะที่ผิดพลาด

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