W pytaniu Jak mogę ujawnić tylko fragment IList <> jedna z odpowiedzi zawierała następujący fragment kodu:
IEnumerable<object> FilteredList()
{
foreach(object item in FullList)
{
if(IsItemInPartialList(item))
yield return item;
}
}
Co tam robi słowo kluczowe wydajności? Widziałem to w kilku miejscach i jeszcze jedno pytanie, ale nie do końca zrozumiałem, co to właściwie robi. Przyzwyczaiłem się do myślenia o wydajności w sensie ustępowania jednego wątku drugiemu, ale tutaj nie wydaje się to istotne.
Odpowiedzi:
Słowo
yield
kluczowe faktycznie tutaj robi całkiem sporo.Funkcja zwraca obiekt implementujący
IEnumerable<object>
interfejs. Jeśli funkcja wywołująca rozpoczynaforeach
się nad tym obiektem, funkcja jest wywoływana ponownie, dopóki „nie ustąpi”. To cukier syntaktyczny wprowadzony w C # 2.0 . We wcześniejszych wersjach trzeba było tworzyć własneIEnumerable
iIEnumerator
obiekty, aby robić takie rzeczy.Najprostszym sposobem na zrozumienie takiego kodu jest wpisanie przykładu, ustawienie niektórych punktów przerwania i sprawdzenie, co się stanie. Spróbuj przejść przez ten przykład:
Po przejściu przez przykład znajdziesz pierwsze wezwanie do
Integers()
powrotu1
. Drugie połączenie powraca2
i liniayield return 1
nie jest wykonywana ponownie.Oto prawdziwy przykład:
źródło
yield break;
gdy nie chcesz zwracać więcej przedmiotów.yield
nie jest słowem kluczowym. Gdyby tak było, nie mógłbym użyć plonu jako identyfikatora jak wint yield = 500;
'If a calling function starts foreach-ing over this object the function is called again until it "yields"'
. nie brzmi dla mnie dobrze. Zawsze myślałem o słowie kluczowym pl # c w kontekście „plony dają obfite plony”, a nie „samochód ustępuje pieszym”.Iteracja. Tworzy maszynę stanową „pod pokrywami”, która pamięta, gdzie byłeś w każdym dodatkowym cyklu funkcji i stamtąd zaczyna.
źródło
Wydajność ma dwa świetne zastosowania,
Pomaga zapewnić niestandardową iterację bez tworzenia tymczasowych kolekcji.
Pomaga wykonać stanową iterację.
Aby wyjaśnić powyższe dwa punkty bardziej demonstracyjnie, stworzyłem prosty film, który można obejrzeć tutaj
źródło
yield
. @ ShivprasadKoirala artykuł dotyczący projektu kodu Jaki jest pożytek z C # Yield? tego samego wyjaśnienia jest również dobrym źródłemyield
jest „szybkim” sposobem na utworzenie niestandardowego IEnumeratora (raczej posiadanie klasy implementującej interfejs IEnumerator).Ostatnio Raymond Chen opublikował również interesującą serię artykułów na temat słowa kluczowego wydajności.
Choć jest nominalnie używany do łatwego wdrażania wzorca iteratora, ale może zostać uogólniony na maszynę stanu. Nie ma sensu cytować Raymonda, ostatnia część zawiera również linki do innych zastosowań (ale przykład na blogu Entina jest szczególnie dobry, pokazując, jak pisać bezpieczny kod asynchroniczny).
źródło
Na pierwszy rzut oka stopa zwrotu wynosi cukier .NET, który zwraca IEnumerable .
Bez zysku wszystkie elementy kolekcji są tworzone jednocześnie:
Ten sam kod przy użyciu wydajności, zwraca pozycję po pozycji:
Zaletą korzystania z wydajności jest to, że jeśli funkcja wykorzystująca dane potrzebuje tylko pierwszego elementu kolekcji, reszta elementów nie zostanie utworzona.
Operator plonu pozwala na tworzenie elementów zgodnie z zapotrzebowaniem. To dobry powód, aby z niego skorzystać.
źródło
yield return
jest używany z modułami wyliczającymi. Przy każdym zestawieniu deklaracji dochodu kontrola jest zwracana do osoby dzwoniącej, ale zapewnia utrzymanie stanu odbiorcy. Z tego powodu, gdy wywołujący wylicza następny element, kontynuuje wykonywanie w metodzie odbierającej z instrukcji bezpośrednio poyield
instrukcji.Spróbujmy to zrozumieć na przykładzie. W tym przykładzie, odpowiadającemu każdej linii, wspomniałem kolejność, w jakiej przebiega wykonanie.
Ponadto stan jest zachowywany dla każdego wyliczenia. Załóżmy, że mam inne wywołanie
Fibs()
metody, a następnie stan zostanie zresetowany.źródło
Intuicyjnie słowo kluczowe zwraca wartość z funkcji bez jej opuszczania, tzn. W twoim przykładzie kodu zwraca bieżącą
item
wartość, a następnie wznawia pętlę. Bardziej formalnie, jest on używany przez kompilator do generowania kodu dla iteratora . Iteratory to funkcje zwracająceIEnumerable
obiekty. MSDN ma kilka artykułów na ich temat.źródło
Implementacja listy lub tablicy natychmiast ładuje wszystkie elementy, a implementacja wydajności zapewnia odroczone wykonanie.
W praktyce często pożądane jest wykonanie minimalnej ilości pracy w razie potrzeby w celu zmniejszenia zużycia zasobów przez aplikację.
Na przykład możemy mieć aplikację przetwarzającą miliony rekordów z bazy danych. Następujące korzyści można osiągnąć, używając IEnumerable w modelu opartym na odroczonym wykonywaniu wykonania:
Oto porównanie najpierw zbuduj kolekcję, taką jak lista, w porównaniu do wykorzystania wydajności.
Przykład listy
Dane wyjściowe konsoli
ContactListStore: Tworzenie kontaktu 1
ContactListStore: Tworzenie kontaktu 2
ContactListStore: Tworzenie kontaktu 3
Gotowy do iteracji przez kolekcję.
Uwaga: cała kolekcja została załadowana do pamięci, nawet nie pytając o pojedynczy element na liście
Przykład wydajności
Dane wyjściowe konsoli
Gotowe do iteracji po kolekcji.
Uwaga: kolekcja nie została w ogóle wykonana. Wynika to z charakteru „odroczonego wykonania” IEnumerable. Konstruowanie przedmiotu nastąpi tylko wtedy, gdy jest naprawdę wymagane.
Nazwijmy ponownie kolekcję i odwróćmy zachowanie, gdy pobieramy pierwszy kontakt w kolekcji.
Dane wyjściowe konsoli
Gotowe do iteracji w kolekcji
ContactYieldStore: Tworzenie kontaktu 1
Hello Bob
Miły! Tylko pierwszy kontakt został nawiązany, gdy klient „wyciągnął” element z kolekcji.
źródło
Oto prosty sposób na zrozumienie tej koncepcji: podstawową ideą jest to, że jeśli chcesz mieć kolekcję, z której możesz korzystać „
foreach
”, ale zebranie elementów do kolekcji jest z jakiegoś powodu kosztowne (np. Wysłanie zapytania do bazy danych), ORAZ często nie potrzebujesz całej kolekcji, a następnie tworzysz funkcję, która buduje kolekcję po jednym elemencie i zwraca ją konsumentowi (który może wcześniej zakończyć zbiórkę).Pomyśl o tym w ten sposób: idziesz do kontuaru z mięsem i chcesz kupić funt pokrojonej szynki. Rzeźnik bierze z tyłu 10-funtową szynkę, kładzie ją na maszynie do krojenia, kroi całość, a następnie przynosi stos plasterków z powrotem i odmierza funt. (Stara droga). Za
yield
pomocą rzeźnika przynosi maszynę do krojenia i zaczyna kroić i „wydawać” każdy plasterek na skali, aż osiągnie 1 funt, a następnie owija go dla Ciebie i gotowe. Stara Droga może być lepsza dla rzeźnika (pozwala mu organizować swoje maszyny tak, jak mu się podoba), ale Nowa Droga jest w większości przypadków bardziej wydajna dla konsumenta.źródło
Słowo
yield
kluczowe umożliwia utworzenieIEnumerable<T>
formularza w bloku iteratora . Ten blok iteratora obsługuje odraczanie wykonywania, a jeśli nie znasz tej koncepcji, może wydawać się niemal magiczny. Jednak na koniec dnia to tylko kod, który wykonuje się bez dziwnych sztuczek.Blok iteratora można opisać jako cukier syntaktyczny, w którym kompilator generuje maszynę stanu, która śledzi, jak daleko posunęło się wyliczenie liczby. Aby wyliczyć wyliczenie, często używasz
foreach
pętli. Jednakżeforeach
pętli jest również cukier składniowym. Jesteś więc dwiema abstrakcjami usuniętymi z prawdziwego kodu, dlatego początkowo może być trudno zrozumieć, jak to wszystko działa razem.Załóżmy, że masz bardzo prosty blok iteratora:
Rzeczywiste bloki iteratora często mają warunki i pętle, ale kiedy sprawdzasz warunki i rozwijasz pętle, nadal kończą się jako
yield
instrukcje przeplatane z innym kodem.Do wyliczenia bloku iteratora
foreach
używana jest pętla:Oto wynik (tutaj nie ma niespodzianek):
Jak wspomniano powyżej,
foreach
cukier syntaktyczny:Próbując rozwiązać ten problem, stworzyłem schemat sekwencji z usuniętymi abstrakcjami:
Automat stanów wygenerowany przez kompilator również implementuje moduł wyliczający, ale dla uproszczenia diagramu pokazałem je jako osobne instancje. (Gdy automat stanowy jest wyliczany z innego wątku, faktycznie otrzymujesz osobne instancje, ale ten szczegół nie jest tutaj ważny).
Za każdym razem, gdy wywołujesz blok iteratora, tworzona jest nowa instancja automatu stanów. Jednak żaden kod w bloku iteratora nie jest wykonywany, dopóki nie zostanie wykonany
enumerator.MoveNext()
po raz pierwszy. Tak działa odroczone wykonywanie. Oto (raczej głupi) przykład:W tym momencie iterator nie wykonał się.
Where
Klauzula tworzy nowyIEnumerable<T>
, który owijaIEnumerable<T>
zwrócony przezIteratorBlock
ale przeliczalna musi jeszcze zostać wymienione. Dzieje się tak, gdy wykonujeszforeach
pętlę:Jeśli wyliczyć wyliczenie dwa razy, to za każdym razem tworzona jest nowa instancja automatu stanów, a blok iteratora wykona dwukrotnie ten sam kod.
Należy zauważyć, że metody LINQ jak
ToList()
,ToArray()
,First()
,Count()
itp użyjeforeach
pętli wyliczyć przeliczalnego. Na przykładToList()
wyliczy wszystkie elementy tego wyliczenia i zapisze je na liście. Możesz teraz uzyskać dostęp do listy, aby uzyskać wszystkie elementy wyliczenia bez ponownego wykonywania bloku iteratora. Występuje kompromis między wykorzystaniem procesora do wielokrotnego tworzenia elementów wyliczalnych a pamięcią do przechowywania elementów wyliczenia w celu uzyskania do nich dostępu wiele razy przy użyciu metod takich jakToList()
.źródło
Jeśli dobrze to rozumiem, oto jak sformułowałbym to z perspektywy funkcji implementującej IEnumerable z wydajnością.
źródło
Mówiąc prościej, słowo kluczowe wydajności C # pozwala na wiele wywołań do kodu, zwanego iteratorem, który wie, jak powrócić, zanim zostanie zrobiony, a po ponownym wywołaniu kontynuuje tam, gdzie zostało przerwane - tzn. Pomaga iteratorowi stają się przezroczyste dla każdego elementu w sekwencji, którą iterator zwraca w kolejnych wywołaniach.
W JavaScript ta sama koncepcja nazywa się Generatory.
źródło
Jest to bardzo prosty i łatwy sposób na utworzenie wyliczenia dla twojego obiektu. Kompilator tworzy klasę, która otacza twoją metodę i implementuje w tym przypadku IEnumerable <object>. Bez słowa kluczowego fed musiałbyś utworzyć obiekt, który implementuje IEnumerable <object>.
źródło
Wytwarza niezliczoną sekwencję. W rzeczywistości tworzy lokalną sekwencję IEnumerable i zwraca ją jako wynik metody
źródło
Ten link ma prosty przykład
Tutaj są jeszcze prostsze przykłady
Zauważ, że zwrot z wydajności nie powróci z metody. Możesz nawet umieścić
WriteLine
poyield return
Powyższe daje IEnumerable 4 ints 4,4,4,4
Tutaj z
WriteLine
. Doda 4 do listy, wydrukuje abc, następnie doda 4 do listy, a następnie dokończy metodę i tak naprawdę powróci z metody (po zakończeniu metody, tak jak by to było w przypadku procedury bez powrotu). Ale miałaby wartość,IEnumerable
listęint
s, którą zwraca po zakończeniu.Zauważ też, że kiedy używasz dochodu, to, co zwracasz, nie jest tego samego typu co funkcja. Jest to rodzaj elementu na
IEnumerable
liście.Dochodu używasz z typem zwrotu metody jako
IEnumerable
. Jeśli typem zwracanym przez metodę jestint
lubList<int>
używaszyield
, to nie zostanie skompilowana. Możesz użyćIEnumerable
metody zwracania metody bez wydajności, ale wydaje się, że nie możesz użyć wydajności bezIEnumerable
typu zwrotu metody.Aby go wykonać, musisz wywołać go w specjalny sposób.
źródło
public static IEnumerable<TResult> testYieldc<TResult>(TResult t) { yield return t; }
ipublic static IEnumerable<TResult> testYieldc<TResult>(TResult t) { return new List<TResult>(); }
yield return
tego dobrze (poza prostą rzeczą, o której wspomniałem), i nie używałem go zbyt często i nie wiem zbyt wiele o jego zastosowaniach, nie sądzę, że powinien to być zaakceptowany.Jednym z głównych punktów na temat słowa kluczowego Yield jest Lazy Execution . Teraz mam na myśli Lazy Execution, aby wykonać w razie potrzeby. Lepszym sposobem na wyrażenie tego jest podanie przykładu
Przykład: Nieużywanie Yield, tzn. Brak leniwego wykonania.
Przykład: użycie Yield, tj. Lazy Execution.
Teraz, gdy wywołam obie metody.
zauważysz, że listItems będzie zawierał 5 elementów (najedź myszką na listItems podczas debugowania). Podczas gdy fedItems będzie miał tylko odwołanie do metody, a nie do pozycji. Oznacza to, że nie wykonał procesu uzyskiwania przedmiotów w metodzie. Bardzo wydajny sposób uzyskiwania danych tylko w razie potrzeby. Rzeczywistą implementację wydajności można zaobserwować w ORM, takich jak Entity Framework i NHibernate itp.
źródło
Próbuje wprowadzić Rubinową Dobroć :)
Koncepcja: Oto przykładowy kod Ruby, który wypisuje każdy element tablicy
Implementacja każdej metody tablicy daje kontrolę nad wywołującym („wstawia x”), a każdy element tablicy jest starannie przedstawiony jako x. Osoba dzwoniąca może wtedy zrobić wszystko, co musi zrobić z x.
Jednak .Net nie idzie tutaj aż tak daleko. Wydaje się, że C # połączył wydajność z IEnumerable, w sposób zmuszający do napisania pętli foreach w wywołującym, jak widać w odpowiedzi Mendelt. Trochę mniej elegancko.
źródło
yield
jest sprzężony zIEnumerable
, a C # nie ma Rubinowej koncepcji „bloku”. Ale C # ma lambdy, które mogłyby pozwolić na implementacjęForEach
metody, podobnie jak Rubyeach
. Nie oznacza to jednak, że byłoby to dobrym pomysłem .