Wydajność słów kluczowych jest jednym z tych słów kluczowych w języku C #, który nadal mnie zagadką, a ja nigdy nie byłem przekonany, że używam go poprawnie.
Który z poniższych dwóch fragmentów kodu jest preferowany i dlaczego?
Wersja 1: Korzystanie ze zwrotu z zysków
public static IEnumerable<Product> GetAllProducts()
{
using (AdventureWorksEntities db = new AdventureWorksEntities())
{
var products = from product in db.Product
select product;
foreach (Product product in products)
{
yield return product;
}
}
}
Wersja 2: zwraca listę
public static IEnumerable<Product> GetAllProducts()
{
using (AdventureWorksEntities db = new AdventureWorksEntities())
{
var products = from product in db.Product
select product;
return products.ToList<Product>();
}
}
c#
yield-return
senfo
źródło
źródło
yield
jest przywiązanyIEnumerable<T>
i tego rodzaju. Jest to w jakiś sposób leniwa ocenayield return
jeśli kod, który iteruje wyniki,GetAllProducts()
pozwala użytkownikowi przedwcześnie anulować przetwarzanie.Odpowiedzi:
Zazwyczaj używam zwrotu z zysku, gdy obliczam następny element na liście (lub nawet następną grupę elementów).
Korzystając z wersji 2, musisz mieć pełną listę przed powrotem. Korzystając ze zwrotu z inwestycji, naprawdę musisz mieć tylko następny przedmiot przed zwrotem.
Pomaga to między innymi rozłożyć koszt obliczeniowy skomplikowanych obliczeń w dłuższych ramach czasowych. Na przykład, jeśli lista jest podłączona do GUI, a użytkownik nigdy nie przechodzi do ostatniej strony, nigdy nie oblicza się końcowych pozycji na liście.
Innym przypadkiem, w którym preferowany jest zwrot z zysku, jest to, że IEnumerable reprezentuje zestaw nieskończony. Rozważ listę liczb pierwszych lub nieskończoną listę liczb losowych. Nigdy nie możesz zwrócić pełnego IEnumerable na raz, więc używasz return-return, aby zwracać listę przyrostowo.
W twoim przykładzie masz pełną listę produktów, więc użyłbym wersji 2.
źródło
Yield return
wydaje się być skrótem do pisania własnej niestandardowej klasy iteratora (zaimplementuj IEnumerator). Dlatego wspomniane korzyści dotyczą również niestandardowych klas iteratorów. W każdym razie obie konstrukcje zachowują stan pośredni. W najprostszej postaci chodzi o trzymanie odniesienia do bieżącego obiektu.Wypełnianie listy tymczasowej jest jak pobieranie całego wideo, podczas gdy używanie
yield
jest jak przesyłanie strumieniowe tego wideo.źródło
Jako koncepcyjny przykład zrozumienia, kiedy powinieneś użyć
yield
, powiedzmy, że metodaConsumeLoop()
przetwarza elementy zwrócone / uzyskane przezProduceList()
:Bez
yield
tego połączenieProduceList()
może zająć dużo czasu, ponieważ musisz wypełnić listę przed zwróceniem:Za pomocą
yield
zmienia się, działa jakby „równolegle”:I wreszcie, jak wielu wcześniej sugerowało, powinieneś użyć wersji 2, ponieważ i tak masz już ukończoną listę.
źródło
Wiem, że to stare pytanie, ale chciałbym podać jeden przykład, w jaki sposób można twórczo wykorzystać słowo kluczowe wydajności. I rzeczywiście skorzystało z tej techniki. Mam nadzieję, że będzie to pomocne dla każdego, kto natknie się na to pytanie.
Uwaga: nie myśl o słowu kluczowym wydajności jako o innym sposobie budowania kolekcji. Duża część siły dochodu polega na tym, że wykonanie jest wstrzymane w metodzie lub właściwości, dopóki kod wywołujący nie przejdzie do następnej wartości. Oto mój przykład:
Użycie słowa kluczowego fed (wraz z implementacją Caliburn.Micro Coroutines Roba Eisenburga ) pozwala mi wyrazić asynchroniczne wywołanie usługi internetowej:
Spowoduje to włączenie mojego BusyIndicator, wywołanie metody Login w mojej usłudze internetowej, ustawienie flagi IsLoggedIn na wartość zwracaną, a następnie wyłączenie BusyIndicator.
Oto jak to działa: IResult ma metodę wykonania i zdarzenie zakończone. Caliburn.Micro pobiera IEnumerator z wywołania HandleButtonClick () i przekazuje go do metody Coroutine.BeginExecute. Metoda BeginExecute rozpoczyna iterację poprzez IResults. Kiedy zwracany jest pierwszy IResult, wykonywanie jest wstrzymywane w HandleButtonClick (), a BeginExecute () dołącza moduł obsługi zdarzenia do zdarzenia Completed i wywołuje Execute (). IResult.Execute () może wykonać zadanie synchroniczne lub asynchroniczne i po zakończeniu uruchamia zdarzenie Completed.
LoginResult wygląda mniej więcej tak:
Może pomóc skonfigurować coś takiego i przejść przez wykonanie, aby zobaczyć, co się dzieje.
Mam nadzieję, że to komuś pomoże! Naprawdę podobało mi się odkrywanie różnych sposobów wykorzystania plonu.
źródło
yield
w ten sposób. Wydaje się to być eleganckim sposobem na emulację wzorca asynchronizacji / oczekiwania (który, jak zakładam, zostałby użyty zamiast,yield
gdyby został przepisany dzisiaj). Czy odkryłeś, że te twórcze zastosowaniayield
przyniosły (bez zamierzonej gry słów) malejące zwroty w miarę upływu lat, gdy C # ewoluował od czasu odpowiedzi na to pytanie? A może wciąż masz zmodernizowane sprytne przypadki użycia, takie jak ten? A jeśli tak, czy mógłbyś podzielić się z nami kolejnym interesującym scenariuszem?To może wydawać się dziwną sugestią, ale nauczyłem się używać
yield
słowa kluczowego w C #, czytając prezentację na temat generatorów w Pythonie: David M. Beazley's http://www.dabeaz.com/generators/Generators.pdf . Nie musisz znać dużo języka Python, aby zrozumieć prezentację - ja nie. Uznałem, że jest to bardzo pomocne w wyjaśnieniu nie tylko tego, jak działają generatory, ale dlaczego warto się tym przejmować.źródło
Zwrot wydajności może być bardzo silny w przypadku algorytmów, w których trzeba iterować przez miliony obiektów. Rozważ następujący przykład, w którym musisz obliczyć możliwe przejazdy w celu współdzielenia jazdy. Najpierw generujemy możliwe wycieczki:
Następnie powtarzaj każdą podróż:
Jeśli użyjesz List zamiast wydajności, będziesz musiał przydzielić 1 milion obiektów do pamięci (~ 190mb), a ten prosty przykład zajmie ~ 1400ms. Jeśli jednak użyjesz wydajności, nie musisz umieszczać wszystkich tych obiektów tymczasowych w pamięci, a uzyskasz znacznie większą szybkość algorytmu: ten przykład zajmie tylko ~ 400 ms bez użycia pamięci.
źródło
yield
działa pod przykryciem, implementując wewnętrznie maszynę stanu. Oto odpowiedź SO z 3 szczegółowymi postami na blogu MSDN, które szczegółowo wyjaśniają wdrożenie. Wpisany przez Raymond Chen @ MSFTDwa fragmenty kodu naprawdę robią dwie różne rzeczy. Pierwsza wersja pobierze członków tak, jak ich potrzebujesz. Druga wersja załaduje wszystkie wyniki do pamięci, zanim zaczniesz z nią cokolwiek robić.
Nie ma dobrej lub złej odpowiedzi na to pytanie. Który z nich jest lepszy, zależy tylko od sytuacji. Na przykład, jeśli istnieje limit czasu na wypełnienie zapytania i musisz zrobić coś częściowo skomplikowanego z wynikami, druga wersja może być lepsza. Uważaj jednak na duże zestawy wyników, zwłaszcza jeśli uruchamiasz ten kod w trybie 32-bitowym. Kilka razy zostałem ugryziony przez wyjątki OutOfMemory podczas wykonywania tej metody.
Kluczową rzeczą, o której należy pamiętać, jest to: różnice w wydajności. Dlatego prawdopodobnie powinieneś wybrać ten, który upraszcza kod i zmienić go dopiero po profilowaniu.
źródło
Wydajność ma dwa świetne zastosowania
Pomaga zapewnić niestandardową iterację bez tworzenia kolekcji tymczasowych. (ładowanie wszystkich danych i zapętlanie)
Pomaga wykonać stanową iterację. (streaming)
Poniżej znajduje się prosty film, który stworzyłem z pełną demonstracją w celu wsparcia powyższych dwóch punktów
http://www.youtube.com/watch?v=4fju3xcm21M
źródło
Oto, co Chris Sells mówi o tych wypowiedziach w języku programowania C # ;
źródło
F().Any()
- wróci po próbie wyliczenia tylko pierwszego wyniku. Ogólnie rzecz biorąc, nie powinieneś polegać naIEnumerable yield
zmianie stanu programu, ponieważ może on nie zostać uruchomionyZakładając, że twoje produkty klasa LINQ używa podobnej wydajności do wyliczania / iteracji, pierwsza wersja jest bardziej wydajna, ponieważ daje tylko jedną wartość za każdym razem, gdy jest iterowana.
Drugi przykład to konwersja modułu wyliczającego / iteratora na listę za pomocą metody ToList (). Oznacza to, że ręcznie iteruje wszystkie elementy modułu wyliczającego, a następnie zwraca płaską listę.
źródło
Jest to trochę poza tym, ale ponieważ pytanie jest oznaczone najlepszymi praktykami, pójdę dalej i wrzucę moje dwa centy. W przypadku tego typu rzeczy zdecydowanie wolę przekształcić je we właściwość:
Jasne, to trochę więcej płyt kotłowych, ale kod, który tego używa, będzie wyglądał znacznie lepiej:
vs
Uwaga: nie zrobiłbym tego w przypadku metod, które mogłyby zająć trochę czasu, aby wykonać swoją pracę.
źródło
A co z tym?
Myślę, że to jest o wiele czystsze. Nie mam jednak pod ręką VS2008 do sprawdzenia. W każdym razie, jeśli Produkty implementują IEnumerable (jak się wydaje - jest używana w instrukcji foreach), zwrócę to bezpośrednio.
źródło
W tym przypadku użyłbym wersji 2 kodu. Ponieważ dostępna jest pełna lista produktów i tego właśnie oczekuje „konsument” wywołania tej metody, konieczne będzie odesłanie pełnej informacji z powrotem do osoby dzwoniącej.
Jeśli osoba wywołująca tę metodę wymaga „jednej” informacji na raz, a zużycie następnej informacji odbywa się na żądanie, wówczas korzystne byłoby zastosowanie zwrotu z zysku, który zapewni, że polecenie wykonania zostanie zwrócone do osoby dzwoniącej, gdy dostępna jest jednostka informacji.
Oto kilka przykładów, w których można zastosować zwrot z zysków:
Aby odpowiedzieć na twoje pytania, użyłbym wersji 2.
źródło
Zwróć listę bezpośrednio. Korzyści:
Lista jest wielokrotnego użytku. (iterator nie jest)nie jest prawdą, dzięki JonPowinieneś używać iteratora (wydajności) od momentu, gdy uważasz, że prawdopodobnie nie będziesz musiał iterować aż do końca listy lub gdy nie ma ona końca. Na przykład, wywołanie klienta będzie szukało pierwszego produktu, który spełnia niektóre predykaty, możesz rozważyć użycie iteratora, chociaż jest to wymyślony przykład i są prawdopodobnie lepsze sposoby na osiągnięcie tego. Zasadniczo, jeśli wiesz z góry, że trzeba będzie obliczyć całą listę, po prostu zrób to z góry. Jeśli uważasz, że tak nie będzie, rozważ użycie wersji iteratora.
źródło
Keyphrase zwrotu wydajności służy do utrzymywania automatu stanów dla określonej kolekcji. Ilekroć CLR widzi używaną frazę zwrotu zysku, CLR implementuje wzorzec modułu wyliczającego do tego fragmentu kodu. Ten rodzaj implementacji pomaga programistom we wszystkich instalacjach hydraulicznych, które w innym przypadku musielibyśmy zrobić bez słowa kluczowego.
Załóżmy, że programista filtruje jakąś kolekcję, iteruje kolekcję, a następnie wyodrębnia te obiekty w nowej kolekcji. Tego rodzaju hydraulika jest dość monotonna.
Więcej informacji o tym słowie kluczowym znajduje się w tym artykule .
źródło
Wykorzystanie wydajności jest podobne do słowa kluczowego return , z tym wyjątkiem, że zwróci generator . A generator obiekt będzie przechodzić tylko raz .
plon ma dwie zalety:
Istnieje inne jasne wytłumaczenie, które może ci pomóc.
źródło