Tüm numaralandırmaları bir kerede verim iadesi ile iade edin; Dönmeden


164

Bir kart için doğrulama hataları almak için aşağıdaki işlevi var. Sorum GetErrors ile uğraşmakla ilgili. Her iki yöntem de aynı dönüş türüne sahiptir 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
}

İçindeki tüm hataları döndürmek mümkün mü GetMoreErrors numaralandırmaya gerek kalmadan ?

Bunu düşünmek muhtemelen aptalca bir soru, ama yanlış gitmediğimden emin olmak istiyorum.


Daha fazla getiri getirisi sorusunun geldiğini gördüğüm için mutluyum (ve meraklıyım!) - Kendimi tam olarak anlamıyorum. Aptalca bir soru değil!
JoshJordan

Nedir GetCardProductionValidationErrorsFor?
Andrew Hare

4
dönüş GetMoreErrors (kart) ile yanlış olan ; ?
Sam Saffron

10
@Sam: "daha fazla doğrulama hataları daha fazla verim döner"
Jon Skeet

1
Belirsiz olmayan bir dil açısından, bir sorun, yöntemin hem T hem de IEnumerable <T> uygulayan bir şey olup olmadığını bilemeyeceğidir. Yani verimde farklı bir yapıya ihtiyacınız var. Bununla birlikte, bunu yapmanın bir yolu olması iyi olurdu dedi. Getiri getirisi foo, belki de, foo IEnumerable <T> 'ı nerede uygular?
William Jockusch

Yanıtlar:


141

Kesinlikle aptalca bir soru değil ve F # 'ın tek bir yield!ürüne karşı tüm koleksiyon için desteklediği bir şey yield. (Bu, kuyruk özyineleme açısından çok yararlı olabilir ...)

Maalesef C # 'da desteklenmiyor.

Ancak, her birini döndüren birkaç yönteminiz IEnumerable<ErrorInfo>varsa, Enumerable.Concatkodunuzu daha basit hale getirmek için kullanabilirsiniz :

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

Bununla birlikte, iki uygulama arasında çok önemli bir fark vardır: bu , döndürülen yineleyicileri birer birer kullanmasına rağmen, tüm yöntemleri derhal çağıracaktır . Mevcut kodunuz , bir sonraki hataları sormadanGetMoreErrors() önce her şeyde döngü yapana kadar bekleyecektir .

Genellikle bu önemli değildir, ancak ne zaman olacağını anlamaya değer.


3
Wes Dyer'in bu modelden bahseden ilginç bir makalesi var. blogs.msdn.com/wesdyer/archive/2007/03/23/…
JohannesH

1
Geçenler için küçük düzeltme - bu System.Linq.Enumeration.Concat <> (birinci, ikinci). IEnumeration.Concat () değil.
redcalx

@ the-locster: Ne demek istediğinden emin değilim. Numaralandırma yerine kesinlikle Numaralandırılabilir. Yorumunuzu netleştirebilir misiniz?
Jon Skeet

@Jon Skeet - Yöntemleri derhal çağıracak tam olarak ne demek istiyorsun? Bir test yaptım ve bir şey gerçekten yinelenene kadar yöntem çağrılarını tamamen erteliyor gibi görünüyor. Kod burada: pastebin.com/0kj5QtfD
Steven Oxley

5
@Steven: Hayır. O ediyor arayarak ancak sizin durumunuzda - yöntemleri GetOtherErrors()(vs) kendi ertelenmesi olan sonuçları (bunlar yineleyici blokları kullanılarak uygulanan konum gibi). Yeni bir dizi veya bunun gibi bir şey döndürmek için bunları değiştirmeyi deneyin, ne demek istediğimi göreceksiniz.
Jon Skeet

26

Bunun gibi tüm hata kaynaklarını ayarlayabilirsiniz (yöntem adları Jon Skeet'in cevabından ödünç alınmıştır).

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

Daha sonra aynı anda onları tekrarlayabilirsiniz.

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

Alternatif olarak, hata kaynaklarını ile düzleştirebilirsiniz SelectMany.

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

Yöntemlerin yürütülmesi GetErrorSourcesde gecikecektir.


17

Hızlı bir yield_pasaj buldum :

verim_ kesilmiş kullanım animasyonu

Snippet XML'si şöyledir:

<?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
Bu sorunun cevabı nasıl?
Ian Kemp

1
@Ian, bu C # iç içe verimi dönüş yapmak zorunda. yield!F # 'da olduğu gibi hayır yoktur .
John Gietzen

Bu sorunun cevabı değil
divyang4481

8

İşlevinle ilgili yanlış bir şey görmüyorum, istediğini yaptığını söyleyebilirim.

Verimi, her çağrıldığında son Numaralandırma'da bir öğe döndürme olarak düşünün, böylece foreach döngüsünde böyle olduğunda, her çağrıldığında 1 öğe döndürür. Sonuç kümesini filtrelemek için önbelleğinize koşullu ifadeler koyabilirsiniz. (yalnızca hariç tutma ölçütlerinizi yerine getirmeyerek)

Yöntemin ilerleyen bölümlerinde daha sonra verim eklerseniz, numaralandırmaya 1 öğe eklemeye devam eder ve bu şekilde ...

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

4

Kimse IEnumerable<IEnumerable<T>>bu kod ertelenmiş yürütme tutmak için basit bir uzantısı yöntemi önermek düşündüm şaşırdım . Birçok nedenden ötürü ertelenmiş yürütme hayranıyım, bunlardan biri, bellek ayak izinin büyük mongous numaralandırılabilirler için bile küçük olmasıdır.

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

Ve davanızda böyle kullanabilirsiniz

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
}

Benzer şekilde, sarma işlevini ortadan kaldırabilir DoGetErrorsve sadece UnWrapçağrı sitesine gidebilirsiniz.


2
Muhtemelen hiç kimse bir Uzantı yöntemini düşünmedi çünkü DoGetErrors(card).SelectMany(x => x)aynı şeyi yapıyor ve ertelenmiş davranışı koruyor. Bu tam olarak Adem'in cevabında önerdiği şeydir .
huysentruitw

3

Evet, tüm hataları bir kerede geri göndermek mümkündür. Sadece bir List<T>veya döndürün ReadOnlyCollection<T>.

Birini döndürerek bir IEnumerable<T>şey dizisini döndürürsünüz. Yüzeyde koleksiyonun geri dönüşüyle ​​aynı görünebilir, ancak bir dizi fark var, aklınızda bulundurmalısınız.

Koleksiyonları

  • Arayan, koleksiyon döndürüldüğünde hem koleksiyonun hem de tüm öğelerin var olduğundan emin olabilir. Koleksiyonun arama başına oluşturulması gerekiyorsa, koleksiyonun iade edilmesi gerçekten kötü bir fikirdir.
  • Çoğu koleksiyon iade edildiğinde değiştirilebilir.
  • Koleksiyon sonlu boyuttadır.

Diziler

  • Numaralandırılabilir - ve kesin olarak söyleyebileceğimiz hemen hemen hepsi bu.
  • Döndürülen bir dizinin kendisi değiştirilemez.
  • Her eleman, sekanstan geçmenin bir parçası olarak oluşturulabilir (yani geri döndürmek IEnumerable<T>tembel değerlendirmeye, geri döndürmeye izin verirList<T> ).
  • Bir dizi sonsuz olabilir ve bu nedenle kaç öğenin döndürülmesi gerektiğine karar vermek için onu arayana bırakabilir.

Tüm istemciler için veri yapılarını önceden ayırdığınız için, bir istemcinin gerçekten ihtiyacı olan tüm istemcinin numaralandırması ise, bir koleksiyonu döndürmek mantıksız ek yüke neden olabilir. Ayrıca, bir diziyi döndüren başka bir yönteme temsilci seçerseniz, bunu bir koleksiyon olarak yakalamak fazladan kopyalamayı içerir ve bunun kaç öğe (ve dolayısıyla ne kadar ek yük) içerebileceğini bilmiyorsunuzdur. Bu nedenle, toplama zaten orada olduğunda ve kopyalanmadan doğrudan iade edilebildiği (veya salt okunur olarak sarılabildiği) zaman geri dönmek iyi bir fikirdir. Diğer tüm durumlarda, sıra daha iyi bir seçimdir
Pavel Minaev

Katılıyorum ve eğer bir koleksiyonun iadesinin her zaman iyi bir fikir olduğunu söylediğim izlenimini edindiyseniz, benim fikrimi kaçırdınız. Bir koleksiyon döndürme ve bir sekans döndürme arasında farklar olduğunu vurgulamaya çalışıyordum. Daha net yapmaya çalışacağım.
Brian Rasmussen
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.