yield oraz Enumerable

Tak kiedyś pisałem kod:

IEnumerable<int> GetPostsByAuthor(IEnumerable<Post> posts, string author)
{
    var postIds = new List<int>();

    foreach (var post in posts)
    {
        if (post.Author == author)
        {
            postIds.Add(post.Id);
        }
    }

    return postIds;
}

yield

Nie ma w nic złego, robi dokładnie to na co wygląda (przynajmnie tak chciałbym myśleć o samokomentującym się kodzie, który piszę;) Teraz jednak staram się nie tworzyć tej dodatkowej listy i operować na magii yield.

IEnumerable<int> GetPostsByAuthorYield(IEnumerable<Post> posts, string author)
{
    foreach (var post in posts)
    {
        if (post.Author == author)
        {
            yield return post.Id;
        }
    }
}

Drugim słówkiem kluczowym jest yield break. Kończy ono enumerowanie.

IEnumerable<int> GetPostsByAuthorYield(IEnumerable<Post> posts, string author)
{
    if (posts == null || string.IsNullOrEmpty(author))
        yield break;

    foreach (var post in posts)
    {
        if (post.Author == author)
        {
            yield return post.Id;
        }
    }
}

Równie dobrze może ono być w pętli i wtedy enumerowanie skończy się szybciej.

Czasem przydatny jest kod, gdy nie mamy żadnych instrukcji sterujących, a jedynie yield return

IEnumerable<Coordinates> GetNeighbours(int x, int y)
{
    yield return new Coordinates(x - 1, y - 1);
    yield return new Coordinates(x - 1, y);
    yield return new Coordinates(x - 1, y + 1);
    yield return new Coordinates(x, y - 1);
    // pomijamy new Coordinates(x,y)
    yield return new Coordinates(x, y + 1);
    // ...
}

W tym przypadku mamy wygenerować otoczenie punktu (x,y) z pominieciem tego punktu. Można to zrobić za pomocą dwóch pętli i if’a w środku, ale z użyciem yield jest czytelniej.

Enjoy

Update

Dziś natrafiłem na fajny przykład wykorzystania yield. Pobieramy Breadcrumps czyli gdzie w danej chwili znajdujemy się na stronie i jak dalego mamy do Home Page:

public IEnumerable<BreadcrumbItem> GetBreadcrumb(ContentReference currentReference)
{
    if (currentReference.CompareToIgnoreWorkID(ContentReference.StartPage)) yield break;

    var pagePath = _contentLoader.GetAncestors(currentReference)
        .Reverse()
        .Select(x => x.ContentLink)
        .SkipWhile(x => !x.CompareToIgnoreWorkID(ContentReference.StartPage))
        .ToList();

    foreach (var page in pagePath.Select(contentReference => _contentLoader.Get<PageData>(contentReference)))
    {
        yield return new BreadcrumbItem(page.PageName, page.PageLink, false);
    }

    var currentPage = _contentLoader.Get<PageData>(currentReference);

    yield return new BreadcrumbItem(currentPage.PageName, currentPage.PageLink, true);
}
Reklamy
Ten wpis został opublikowany w kategorii Programowanie i oznaczony tagami , , , , , . Dodaj zakładkę do bezpośredniego odnośnika.

4 odpowiedzi na „yield oraz Enumerable

  1. lkurzyniec pisze:

    Ostatnimi czasy zrezygnowałem z yield na rzecz Where() z LINQ.

    • Chodzi Ci poniższy kod?
      return posts.Where(p => p.Author == author).Select(p => p.Id);

      Zgadzam się, że w prostych przypadkach to wygląda lepiej i tak bym napisał. Jednak gdy są np 2,3 pętle, a i warunki nietrywialne to wtedy yield może wyglądać naturalniej (a może nie?).

    • Paweł Łuczkiewicz pisze:

      Szczerze mówiąc, sądzę że w większości przypadków LINQ wygląda lepiej(nawet jeśli warunki są długie i skomplikowane, to przecież można je wyciągnąć do osobnych – ładnie nazwanych – metod). Trudno mi jest sobie nawet wyobrazić sytuację, gdy zagnieżdżone pętle wygrywają(ale chętnie zobaczę przykład, bo zapewne jest coś o czym nie pomyślałem :P).

  2. Trudno się z wami nie zgodzić, większość czasu LINQ samoistnie się używa (i jeszcze R# podpowiada). Są jednak sytuację gdy korzystam z yield jak w przykładzie dodanym na końcu wpisu

Możliwość komentowania jest wyłączona.