Czy yield jest przydatny poza LINQ?

Kiedy myślę, że mogę użyć słowa kluczowego yield, cofam się o krok i patrzę, jak wpłynie to na mój projekt. Zawsze kończę zwracając kolekcję zamiast yeilding, ponieważ czuję, że koszty utrzymania stanu metody yeilding nie kupują mi zbyt wiele. W prawie wszystkich przypadkach, w których zwracam zbiór, czuję, że w 90% przypadków metoda wywołująca będzie iteracją wszystkich elementów w zbiorze, lub będzie szukała szeregu elementów w całym zbiorze kolekcja.

Rozumiem jego przydatność w linq, ale wydaje mi się, że tylko zespół linq pisze tak złożone obiekty kwerendy, że yield jest przydatny.

Czy ktos napisal cos podobnego czy nie jak linq gdzie sie przydalo?

Author: Drew Noakes, 2008-11-25

14 answers

Ostatnio musiałem zrobić reprezentację wyrażeń matematycznych w formie klasy wyrażeń. Podczas oceniania wyrażenia muszę przejść przez strukturę drzewa za pomocą powered treewalk. Aby to osiągnąć zaimplementowałem IEnumerable w ten sposób:

public IEnumerator<Expression<T>> GetEnumerator()
{
    if (IsLeaf)
    {
        yield return this;
    }
    else
    {
        foreach (Expression<T> expr in LeftExpression)
        {
            yield return expr;
        }
        foreach (Expression<T> expr in RightExpression)
        {
            yield return expr;
        }
        yield return this;
    }
}

Wtedy mogę po prostu użyć foreach, aby przejść przez wyrażenie. Możesz również dodać właściwość, aby zmienić algorytm trawersowania w razie potrzeby.

 12
Author: Morten Christiansen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-11-25 17:59:08

Zauważ, że w yield powtarzasz zbiór raz, ale kiedy tworzysz listę, będziesz powtarzał ją dwa razy.

Weźmy na przykład iterator filtra:

IEnumerator<T>  Filter(this IEnumerator<T> coll, Func<T, bool> func)
{
     foreach(T t in coll)
        if (func(t))  yield return t;
}

Teraz możesz połączyć to:

 MyColl.Filter(x=> x.id > 100).Filter(x => x.val < 200).Filter (etc)

Twoja metoda będzie tworzyć (i rzucać) trzy listy. Moja metoda powtarza się tylko raz.

Również, gdy zwracasz kolekcję, wymuszasz na użytkownikach określoną implementację. Iterator jest bardziej ogólny.

 27
Author: James Curran,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-11-25 15:50:21

Rozumiem jego przydatność w linq, ale czuję, że tylko zespół linq pisze tak złożone obiekty kwerendy, że yield jest przydatny.

Yield był przydatny, gdy tylko został zaimplementowany w. NET 2.0, co było na długo zanim ktokolwiek pomyślał o LINQ.

Dlaczego miałbym napisać tę funkcję:

IList<string> LoadStuff() {
  var ret = new List<string>();
  foreach(var x in SomeExternalResource)
    ret.Add(x);
  return ret;
}

Kiedy Mogę użyć yield, i zaoszczędzić wysiłek i złożoność tworzenia tymczasowej listy bez powodu:

IEnumerable<string> LoadStuff() {
  foreach(var x in SomeExternalResource)
    yield return x;
}

Może mieć również ogromne zalety wydajności. Jeśli twój kod używa tylko pierwszych 5 elementów kolekcji, użycie yield często uniknie wysiłku ładowania czegokolwiek poza tym punktem. Jeśli zbudujesz kolekcję, a następnie ją zwrócisz, tracisz mnóstwo czasu i przestrzeni ładując rzeczy, których nigdy nie potrzebujesz.

Mógłbym tak dalej....
 18
Author: Orion Edwards,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-12-02 23:51:22

W poprzedniej firmie znalazłem się pisząc takie pętle:

for (DateTime date = schedule.StartDate; date <= schedule.EndDate; 
     date = date.AddDays(1))

Z bardzo prostym blokiem iteratora, udało mi się zmienić to na:

foreach (DateTime date in schedule.DateRange)
Dzięki temu kod jest dużo łatwiejszy do odczytania, IMO.
 11
Author: Jon Skeet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-02-06 05:16:07

yield został opracowany dla C # 2 (przed Linq w C # 3).

Używaliśmy go w dużej korporacyjnej aplikacji internetowej C#2, gdy mieliśmy do czynienia z dostępem do danych i mocno powtarzanymi obliczeniami.

Kolekcje są świetne za każdym razem, gdy masz kilka elementów, które masz zamiar uderzyć wiele razy.

Jednak w wielu scenariuszach dostępu do danych masz dużą liczbę elementów, które niekoniecznie muszą być przekazywane w Wielkiej dużej kolekcji.

To jest w zasadzie to, co SqlDataReader robi - jest to tylko do przodu Niestandardowy enumerator.

To, co yield pozwala Ci zrobić, to szybko i przy minimalnym kodzie napisać własne, niestandardowe wyliczenia.

Wszystko yield może być zrobione w C#1 - wystarczyły tylko ryzy kodu, aby to zrobić.

Linq naprawdę maksymalizuje wartość zachowania plonów, ale z pewnością nie jest to jedyna aplikacja.

 8
Author: Keith,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-11-25 16:12:53

Ilekroć twoja funkcja zwraca IEnumerable powinieneś użyć "yielding". Nie tylko w. Net > 3.0.

. Net 2.0 przykład:

  public static class FuncUtils
  {
      public delegate T Func<T>();
      public delegate T Func<A0, T>(A0 arg0);
      public delegate T Func<A0, A1, T>(A0 arg0, A1 arg1);
      ... 

      public static IEnumerable<T> Filter<T>(IEnumerable<T> e, Func<T, bool> filterFunc)
      {
          foreach (T el in e)
              if (filterFunc(el)) 
                  yield return el;
      }


      public static IEnumerable<R> Map<T, R>(IEnumerable<T> e, Func<T, R> mapFunc)
      {
          foreach (T el in e) 
              yield return mapFunc(el);
      }
        ...
 2
Author: macropas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-11-25 15:21:31

Nie jestem pewien co do implementacji yield () w C#, ale w językach dynamicznych jest to o wiele wydajniejsze niż tworzenie całej kolekcji. w wielu przypadkach ułatwia to pracę z zestawami danych znacznie większymi niż pamięć RAM.

 2
Author: Javier,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-11-25 15:46:09

Jestem wielkim fanem C#. Jest to szczególnie prawdziwe w dużych frameworkach macierzystych, gdzie często metody lub właściwości zwracają listę, która jest podzbiorem innego IEnumerable. Korzyści jakie widzę to:

  • wartość zwracana metody wykorzystującej wydajność jest niezmienna
  • jesteś tylko iterując nad listą raz
  • jest zmienną wykonawczą późno lub leniwie, co oznacza, że kod zwracający wartości nie jest wykonywany dopóki nie jest potrzebny (choć może cię to ugryźć, jeśli nie wiesz co robisz)
  • z listy źródeł zmian, nie musisz wywoływać, aby uzyskać inny IEnumerable, po prostu iterate nad IEnumerable ponownie
  • wiele innych

Jeszcze jedną ogromną zaletą wydajności jest to, że twoja metoda potencjalnie zwróci miliony wartości. Tak wiele, że istnieje możliwość wyczerpania pamięci po prostu budowania listy, zanim metoda może ją zwrócić. Z yield, metoda może po prostu tworzyć i zwracać miliony wartości, a tak długo, jak wywołujący również nie przechowuje każdej wartości. Więc jego dobre dla dużych operacji przetwarzania danych / agregowania

 2
Author: Turbo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-01-27 15:41:19

Osobiście nie znalazłem, że używam yield w moim normalnym codziennym programowaniu. Jednak niedawno zacząłem grać z Robotics Studio sampli i okazało się, że yield jest tam szeroko stosowany, więc widzę również, że jest używany w połączeniu z CCR (współbieżność i koordynacja Runtime), gdzie masz problemy z asynchronią i współbieżnością.

W każdym razie, wciąż staram się to ogarnąć.

 1
Author: Harrison,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-11-25 15:15:43

Wydajność jest przydatna, ponieważ oszczędza miejsce. Większość optymalizacji w programowaniu stanowi kompromis między przestrzenią (Dysk, Pamięć, sieć) a przetwarzaniem. Yield jako konstrukcja programistyczna pozwala na iterację nad zbiorem wiele razy w kolejności bez potrzeby oddzielnej kopii zbioru dla każdej iteracji.

Rozważ ten przykład:

static IEnumerable<Person> GetAllPeople()
{
    return new List<Person>()
    {
        new Person() { Name = "George", Surname = "Bush", City = "Washington" },
        new Person() { Name = "Abraham", Surname = "Lincoln", City = "Washington" },
        new Person() { Name = "Joe", Surname = "Average", City = "New York" }
    };
}

static IEnumerable<Person> GetPeopleFrom(this IEnumerable<Person> people,  string where)
{
    foreach (var person in people)
    {
        if (person.City == where) yield return person;
    }
    yield break;
}

static IEnumerable<Person> GetPeopleWithInitial(this IEnumerable<Person> people, string initial)
{
    foreach (var person in people)
    {
        if (person.Name.StartsWith(initial)) yield return person;
    }
    yield break;
}

static void Main(string[] args)
{
    var people = GetAllPeople();
    foreach (var p in people.GetPeopleFrom("Washington"))
    {
        // do something with washingtonites
    }

    foreach (var p in people.GetPeopleWithInitial("G"))
    {
        // do something with people with initial G
    }

    foreach (var p in people.GetPeopleWithInitial("P").GetPeopleFrom("New York"))
    {
        // etc
    }
}

(Oczywiście nie musisz używać metody yield z rozszerzeniem, to po prostu tworzy potężny paradygmat do myślenia o data.)

Jak widać, jeśli masz wiele z tych metod "filtrowania" (ale może to być każda metoda, która działa na liście osób), możesz połączyć wiele z nich razem bez konieczności dodatkowego miejsca na każdy krok. Jest to jeden ze sposobów podniesienia języka programowania (C#), aby lepiej wyrazić swoje rozwiązania.

Pierwszy efekt uboczny yield polega na tym, że opóźnia on wykonanie logiki filtrowania, dopóki nie będzie ona rzeczywiście potrzebna. Jeśli zatem utworzysz zmienną o wpisz IEnumerable (z plonami), ale nigdy nie powtarzaj go, nigdy nie wykonujesz logiki ani nie zużywasz przestrzeni, która jest potężną i darmową optymalizacją.

Drugi efekt uboczny polega na tym, że yield działa na najniższym wspólnym interfejsie kolekcji (IEnumerable), który umożliwia tworzenie kodu podobnego do biblioteki o szerokim zastosowaniu.

 1
Author: Pieter Breed,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-11-25 15:19:47

Zauważ, że yield pozwala robić rzeczy w "leniwy" sposób. Przez leniwy, mam na myśli, że ocena następnego elementu w IEnumberable nie jest wykonywana, dopóki element nie jest rzeczywiście wymagane. To pozwala Ci zrobić kilka różnych rzeczy. Jednym z nich jest to, że można uzyskać nieskończenie długą listę bez konieczności wykonywania nieskończonych obliczeń. Po drugie, możesz zwrócić wyliczenie aplikacji funkcji. Funkcje będą stosowane tylko w przypadku iteracji poprzez lista.

 1
Author: Logicalmind,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-11-25 17:21:07

Użyłem yeild w kodach innych niż linq takich rzeczy (zakładając, że funkcje nie żyją w tej samej klasie):

public IEnumerable<string> GetData()
{
    foreach(String name in _someInternalDataCollection)
    {
        yield return name;
    }
}

...

public void DoSomething()
{
    foreach(String value in GetData())
    {
        //... Do something with value that doesn't modify _someInternalDataCollection
    }
}

Musisz uważać, aby nie przypadkowo zmodyfikować kolekcji, nad którą iteruje funkcja GetData (), w przeciwnym razie spowoduje to wyrzucenie wyjątku.

 0
Author: Anton,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-11-25 16:03:20

Plon jest bardzo przydatny w ogóle. Jest w Rubim wśród innych języków, które obsługują programowanie w stylu funkcjonalnym, więc jest związany z linq. Bardziej odwrotnie, że linq jest funkcjonalny w stylu, więc używa yield.

MiaĹ 'em problem, w ktĂłrym mój program uĺźywaĺ' duĺźego cpu w niektĂłrych zadaniach w tle. To, czego naprawdę chciałem, to nadal móc pisać funkcje jak normalnie, tak, że mogłem łatwo je odczytać (tj. cały wątek vs.argument oparty na zdarzeniach). I nadal być w stanie rozbić funkcje, jeśli zabrali zbyt dużo procesora. Wydajność jest do tego idealna. Napisałem wpis na blogu na ten temat i źródło jest dostępne dla wszystkich grok :)

 0
Author: Anders Rune Jensen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-12-02 23:32:33

System.Rozszerzenia LINQ IEnumerable są świetne, ale czasami chcesz więcej. Na przykład rozważ następujące rozszerzenie:

public static class CollectionSampling
{
    public static IEnumerable<T> Sample<T>(this IEnumerable<T> coll, int max)
    {
        var rand = new Random();
        using (var enumerator = coll.GetEnumerator());
        {
            while (enumerator.MoveNext())
            {
                yield return enumerator.Current; 
                int currentSample = rand.Next(max);
                for (int i = 1; i <= currentSample; i++)
                    enumerator.MoveNext();
            }
        }
    }    
}

Kolejną interesującą zaletą yieldingu jest to, że wywołujący nie może oddać wartości zwracanej do oryginalnego typu kolekcji i zmodyfikować kolekcji wewnętrznej

 0
Author: Ohad Schneider,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-09-28 13:19:29