คืน enumerables ทั้งหมดพร้อมผลตอบแทนในทันที โดยไม่ต้องวนซ้ำผ่าน


164

ฉันมีฟังก์ชั่นต่อไปนี้เพื่อรับข้อผิดพลาดในการตรวจสอบความถูกต้องของการ์ด คำถามของฉันเกี่ยวข้องกับการจัดการกับ GetErrors IEnumerable<ErrorInfo>ทั้งสองวิธีมีผลตอบแทนประเภทเดียวกัน

private static IEnumerable<ErrorInfo> GetErrors(Card card)
{
    var errors = GetMoreErrors(card);
    foreach (var e in errors)
        yield return e;

    // further yield returns for more validation errors
}

เป็นไปได้ไหมที่จะส่งคืนข้อผิดพลาดทั้งหมดGetMoreErrorsโดยไม่ต้องแจงผ่านพวกเขา

คิดเกี่ยวกับมันอาจเป็นคำถามที่โง่ แต่ฉันต้องการให้แน่ใจว่าฉันจะไม่ผิด


ฉันมีความสุข (และอยากรู้อยากเห็น!) เพื่อดูคำถามที่ให้ผลตอบแทนมากขึ้น - ฉันไม่ค่อยเข้าใจตัวเองมากนัก ไม่ใช่คำถามที่โง่!
JoshJordan

คือGetCardProductionValidationErrorsForอะไร
Andrew Hare

4
เกิดอะไรขึ้นกับการคืน GetMoreErrors (การ์ด); ?
Sam Saffron

10
@ Sam: "ผลตอบแทนที่อัตราผลตอบแทนต่อไปสำหรับข้อผิดพลาดในการตรวจสอบมากขึ้น"
จอนสกีต

1
จากมุมมองของภาษาที่ไม่คลุมเครือปัญหาหนึ่งคือวิธีนี้ไม่สามารถทราบได้ว่ามีสิ่งใดที่ใช้ทั้ง T และ IEnumerable <T> ดังนั้นคุณต้องสร้างโครงสร้างที่แตกต่างกันในผลตอบแทน ที่กล่าวว่ามันจะดีที่มีวิธีการทำเช่นนี้ ผลตอบแทนที่ได้ผลตอบแทน foo, บางที, ที่ foo ใช้ IE จำนวนนับ <T>?
วิลเลียม Jockusch

คำตอบ:


141

ไม่ใช่คำถามที่โง่เง่าแน่นอนและมันเป็นสิ่งที่ F # ให้การสนับสนุนyield!สำหรับคอลเลกชันทั้งหมดเทียบyieldกับรายการเดียว (นั่นอาจมีประโยชน์มากในแง่ของการเรียกซ้ำหาง ... )

น่าเสียดายที่มันไม่รองรับใน C #

อย่างไรก็ตามหากคุณมีหลายวิธีในการส่งคืนIEnumerable<ErrorInfo>คุณสามารถใช้Enumerable.Concatเพื่อทำให้โค้ดของคุณง่ายขึ้น:

private static IEnumerable<ErrorInfo> GetErrors(Card card)
{
    return GetMoreErrors(card).Concat(GetOtherErrors())
                              .Concat(GetValidationErrors())
                              .Concat(AnyMoreErrors())
                              .Concat(ICantBelieveHowManyErrorsYouHave());
}

มีความแตกต่างที่สำคัญอย่างหนึ่งระหว่างการนำไปใช้งานสองอย่าง: วิธีนี้จะเรียกใช้วิธีการทั้งหมดทันทีแม้ว่าจะใช้ตัววนซ้ำที่ส่งกลับทีละครั้งเท่านั้น รหัสที่มีอยู่ของคุณจะรอจนกว่าจะวนซ้ำทุกอย่างGetMoreErrors()ก่อนที่จะถามถึงข้อผิดพลาดต่อไป

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


3
Wes Dyer มีบทความที่น่าสนใจที่กล่าวถึงรูปแบบนี้ blogs.msdn.com/wesdyer/archive/2007/03/23/…
JohannesH

1
การแก้ไขเล็กน้อยสำหรับผู้สัญจร - มันเป็น System.Linq.Enumeration.Concat <> (แรก, วินาที) ไม่ใช่ IEnumeration.Concat ()
redcalx

@ the-locster: ฉันไม่แน่ใจว่าคุณหมายถึงอะไร แน่นอนว่านับได้มากกว่าการแจงนับ คุณช่วยอธิบายความคิดเห็นของคุณได้ไหม?
Jon Skeet

@Jon Skeet - คุณหมายความว่ายังไงมันจะเรียกวิธีการทันที? ฉันทำการทดสอบและดูเหมือนว่ามันเป็นการชะลอการเรียกใช้เมธอดอย่างสมบูรณ์จนกว่าจะมีการทำซ้ำจริง ๆ โค้ดที่นี่: pastebin.com/0kj5QtfD
Steven Oxley

5
@ สตีเว่น: ไม่ มันเรียกวิธีการ - แต่ในกรณีของคุณGetOtherErrors()( ฯลฯ ) มีการชะลอของพวกเขาผล (ที่พวกเขากำลังสร้างโดยใช้บล็อก iterator) ลองเปลี่ยนมันเพื่อคืนค่าอาเรย์ใหม่หรืออะไรทำนองนั้นแล้วคุณจะเห็นว่าฉันหมายถึงอะไร
Jon Skeet

26

คุณสามารถตั้งค่าแหล่งที่มาของข้อผิดพลาดทั้งหมดเช่นนี้ (ชื่อวิธีการยืมจากคำตอบของ Jon Skeet)

private static IEnumerable<IEnumerable<ErrorInfo>> GetErrorSources(Card card)
{
    yield return GetMoreErrors(card);
    yield return GetOtherErrors();
    yield return GetValidationErrors();
    yield return AnyMoreErrors();
    yield return ICantBelieveHowManyErrorsYouHave();
}

จากนั้นคุณสามารถทำซ้ำได้ในเวลาเดียวกัน

private static IEnumerable<ErrorInfo> GetErrors(Card card)
{
    foreach (var errorSource in GetErrorSources(card))
        foreach (var error in errorSource)
            yield return error;
}

SelectManyหรือคุณสามารถแผ่แหล่งข้อผิดพลาดด้วย

private static IEnumerable<ErrorInfo> GetErrors(Card card)
{
    return GetErrorSources(card).SelectMany(e => e);
}

การดำเนินการของวิธีการในGetErrorSourcesจะล่าช้าเกินไป


17

ฉันพบyield_ข้อมูลโค้ดสั้น ๆ :

ภาพเคลื่อนไหวการใช้งาน

นี่คือตัวอย่าง XML:

<?xml version="1.0" encoding="utf-8"?>
<CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
  <CodeSnippet Format="1.0.0">
    <Header>
      <Author>John Gietzen</Author>
      <Description>yield! expansion for C#</Description>
      <Shortcut>yield_</Shortcut>
      <Title>Yield All</Title>
      <SnippetTypes>
        <SnippetType>Expansion</SnippetType>
      </SnippetTypes>
    </Header>
    <Snippet>
      <Declarations>
        <Literal Editable="true">
          <Default>items</Default>
          <ID>items</ID>
        </Literal>
        <Literal Editable="true">
          <Default>i</Default>
          <ID>i</ID>
        </Literal>
      </Declarations>
      <Code Language="CSharp"><![CDATA[foreach (var $i$ in $items$) yield return $i$$end$;]]></Code>
    </Snippet>
  </CodeSnippet>
</CodeSnippets>

2
นี่เป็นคำตอบของคำถามได้อย่างไร
Ian Kemp

1
@Ian นี่คือวิธีที่คุณต้องทำคืนผลตอบแทนที่ซ้อนกันใน C # ไม่มีyield!เช่นใน F #
John Gietzen

นี่ไม่ใช่คำตอบสำหรับคำถาม
divyang4481

8

ฉันไม่เห็นอะไรผิดปกติกับฟังก์ชั่นของคุณฉันจะบอกว่ามันกำลังทำสิ่งที่คุณต้องการ

คิดว่าผลตอบแทนเป็นองค์ประกอบในการแจงนับครั้งสุดท้ายในแต่ละครั้งที่มีการเรียกดังนั้นเมื่อคุณมีมันในวง foreach เช่นนั้นทุกครั้งที่มีการเรียกมันก็จะส่งกลับ 1 องค์ประกอบ คุณสามารถใส่คำสั่งแบบมีเงื่อนไขลงใน foreach เพื่อกรอง resultset ได้ (เพียงโดยไม่ยอมให้เกณฑ์การยกเว้นของคุณ)

หากคุณเพิ่มอัตราผลตอบแทนที่ตามมาในภายหลังวิธีการนั้นจะเพิ่ม 1 องค์ประกอบไปยังการแจงนับทำให้เป็นไปได้ที่จะทำสิ่งต่าง ๆ เช่น ...

public IEnumerable<string> ConcatLists(params IEnumerable<string>[] lists)
{
  foreach (IEnumerable<string> list in lists)
  {
    foreach (string s in list)
    {
      yield return s;
    }
  }
}

4

ฉันประหลาดใจที่ไม่มีใครคิดที่จะแนะนำวิธีการต่อเติมอย่างง่าย ๆIEnumerable<IEnumerable<T>>เพื่อทำให้โค้ดนี้ยังคงทำงานตามปกติ ฉันเป็นแฟนตัวยงของการดำเนินการรอการตัดบัญชีด้วยเหตุผลหลายประการหนึ่งในนั้นคือรอยความทรงจำมีขนาดเล็กแม้สำหรับการนับจำนวนมหาศาล

public static class EnumearbleExtensions
{
    public static IEnumerable<T> UnWrap<T>(this IEnumerable<IEnumerable<T>> list)
    {
        foreach(var innerList in list)
        {
            foreach(T item in innerList)
            {
                yield return item;
            }
        }
    }
}

และคุณสามารถใช้มันในกรณีของคุณเช่นนี้

private static IEnumerable<ErrorInfo> GetErrors(Card card)
{
    return DoGetErrors(card).UnWrap();
}

private static IEnumerable<IEnumerable<ErrorInfo>> DoGetErrors(Card card)
{
    yield return GetMoreErrors(card);

    // further yield returns for more validation errors
}

ในทำนองเดียวกันคุณสามารถทำได้ด้วยฟังก์ชั่นเสื้อคลุมรอบDoGetErrorsและเพียงแค่ย้ายUnWrapไปที่เว็บไซต์


2
อาจไม่มีใครคิดเกี่ยวกับวิธีการส่วนขยายเพราะDoGetErrors(card).SelectMany(x => x)ทำเช่นเดียวกันและรักษาพฤติกรรมที่เลื่อนออกไป ซึ่งเป็นสิ่งที่แสดงให้เห็นอดัมในคำตอบของเขา
huysentruitw

3

ใช่มันเป็นไปได้ที่จะกลับข้อผิดพลาดทั้งหมดในครั้งเดียว เพียงแค่ส่งกลับหรือList<T>ReadOnlyCollection<T>

โดยการส่งคืนIEnumerable<T>คุณกำลังส่งคืนลำดับของบางสิ่ง บนพื้นผิวที่อาจดูเหมือนจะเหมือนกับการคืนคอลเลกชัน แต่มีความแตกต่างจำนวนมากคุณควรจำไว้

คอลเลกชัน

  • ผู้เรียกสามารถมั่นใจได้ว่าทั้งคอลเลกชันและรายการทั้งหมดจะมีอยู่เมื่อมีการส่งคืนคอลเลกชัน หากต้องสร้างคอลเลกชันต่อการโทรการส่งคืนคอลเลกชันเป็นความคิดที่ไม่ดีจริงๆ
  • คอลเลกชันส่วนใหญ่สามารถแก้ไขได้เมื่อส่งคืน
  • คอลเลกชันมีขนาด จำกัด

ลำดับ

  • สามารถแจกแจง - และนั่นก็เป็นสิ่งที่เราสามารถพูดได้อย่างแน่นอน
  • ไม่สามารถแก้ไขลำดับที่ส่งคืนได้
  • แต่ละองค์ประกอบอาจถูกสร้างขึ้นเป็นส่วนหนึ่งของการทำงานผ่านลำดับ (คือกลับมาIEnumerable<T>ช่วยให้การประเมินผลขี้เกียจกลับList<T>ไม่ได้)
  • ลำดับอาจไม่มีที่สิ้นสุดและปล่อยให้ผู้โทรตัดสินใจว่าควรจะคืนองค์ประกอบจำนวนเท่าใด

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

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