Do czego służy słowo kluczowe wydajności w języku C #?


828

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.


Link do MSDN na ten temat znajduje się tutaj msdn.microsoft.com/en-us/library/vstudio/9k7k7cf0.aspx
programista

14
To nie jest zaskakujące. Zamieszanie wynika z faktu, że jesteśmy uwarunkowani, aby widzieć „powrót” jako wynik funkcji, podczas gdy poprzedza ją „wydajność”.
Larry

4
Przeczytałem dokumenty, ale obawiam się, że nadal nie rozumiem :(
Ortund

Odpowiedzi:


737

Słowo yieldkluczowe faktycznie tutaj robi całkiem sporo.

Funkcja zwraca obiekt implementujący IEnumerable<object>interfejs. Jeśli funkcja wywołująca rozpoczyna foreachsię 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łasne IEnumerablei IEnumeratorobiekty, 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:

public void Consumer()
{
    foreach(int i in Integers())
    {
        Console.WriteLine(i.ToString());
    }
}

public IEnumerable<int> Integers()
{
    yield return 1;
    yield return 2;
    yield return 4;
    yield return 8;
    yield return 16;
    yield return 16777216;
}

Po przejściu przez przykład znajdziesz pierwsze wezwanie do Integers()powrotu 1. Drugie połączenie powraca 2i linia yield return 1nie jest wykonywana ponownie.

Oto prawdziwy przykład:

public IEnumerable<T> Read<T>(string sql, Func<IDataReader, T> make, params object[] parms)
{
    using (var connection = CreateConnection())
    {
        using (var command = CreateCommand(CommandType.Text, sql, connection, parms))
        {
            command.CommandTimeout = dataBaseSettings.ReadCommandTimeout;
            using (var reader = command.ExecuteReader())
            {
                while (reader.Read())
                {
                    yield return make(reader);
                }
            }
        }
    }
}

113
W tym przypadku byłoby to łatwiejsze, po prostu używam tutaj liczby całkowitej, aby pokazać, jak działa zwrot z zysku. Zaletą korzystania ze zwrotu z wydajności jest to, że jest to bardzo szybki sposób na implementację wzorca iteratora, więc rzeczy są oceniane leniwie.
Mendelt,

111
Warto również zauważyć, że możesz użyć, yield break;gdy nie chcesz zwracać więcej przedmiotów.
Rory

7
yieldnie jest słowem kluczowym. Gdyby tak było, nie mógłbym użyć plonu jako identyfikatora jak wint yield = 500;
Brandin

5
@Brandin, ponieważ wszystkie języki programowania obsługują dwa typy słów kluczowych, a mianowicie zastrzeżone i kontekstowe. wydajność należy do późniejszej kategorii, dlatego Twój kod nie jest zabroniony przez kompilator C #. Więcej informacji tutaj: ericlippert.com/2009/05/11/reserved-and-contextual-ke words Będziesz zachwycony, że istnieją również słowa zastrzeżone, które nie są rozpoznawane jako słowa kluczowe przez język. Na przykład goto w java. Więcej informacji tutaj: stackoverflow.com/questions/2545103/…
RBT

7
'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”.
Zack

369

Iteracja. Tworzy maszynę stanową „pod pokrywami”, która pamięta, gdzie byłeś w każdym dodatkowym cyklu funkcji i stamtąd zaczyna.


210

Wydajność ma dwa świetne zastosowania,

  1. Pomaga zapewnić niestandardową iterację bez tworzenia tymczasowych kolekcji.

  2. Pomaga wykonać stanową iterację. wprowadź opis zdjęcia tutaj

Aby wyjaśnić powyższe dwa punkty bardziej demonstracyjnie, stworzyłem prosty film, który można obejrzeć tutaj


13
Film pomaga mi zrozumieć yield. @ ShivprasadKoirala artykuł dotyczący projektu kodu Jaki jest pożytek z C # Yield? tego samego wyjaśnienia jest również dobrym źródłem
Dush

Dodałbym również jako trzeci punkt, który yieldjest „szybkim” sposobem na utworzenie niestandardowego IEnumeratora (raczej posiadanie klasy implementującej interfejs IEnumerator).
MrTourkos,

Obejrzałem Twój film Shivprasad i wyraźnie wyjaśnił użycie słowa kluczowego wydajności.
Tore Aurstad

Dzięki za film! Bardzo dobrze wyjaśnione!
Roblogic,

Świetne wideo, ale zastanawiające ... Implementacja z wydajnością jest oczywiście czystsza, ale musi zasadniczo tworzyć własną pamięć tymczasową lub / i Listę wewnętrznie, aby śledzić stan (lub raczej tworzyć maszynę stanu). Czy zatem „Yield” robi coś innego niż upraszczanie implementacji i poprawianie wyglądu, czy może jest coś jeszcze? A co z wydajnością, czy uruchamianie kodu przy użyciu Yield jest mniej lub bardziej wydajne / szybkie niż bez?
toughQuestions

135

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).


To musi zostać poddane pod głosowanie. Słodko, jak wyjaśnia cel operatora i elementów wewnętrznych.
sajidnizami

3
część 1 wyjaśnia składniowy cukier „zwrotu z zysków”. doskonałe wyjaśnienie!
Dror Weiss,

99

Na pierwszy rzut oka stopa zwrotu wynosi cukier .NET, który zwraca IEnumerable .

Bez zysku wszystkie elementy kolekcji są tworzone jednocześnie:

class SomeData
{
    public SomeData() { }

    static public IEnumerable<SomeData> CreateSomeDatas()
    {
        return new List<SomeData> {
            new SomeData(), 
            new SomeData(), 
            new SomeData()
        };
    }
}

Ten sam kod przy użyciu wydajności, zwraca pozycję po pozycji:

class SomeData
{
    public SomeData() { }

    static public IEnumerable<SomeData> CreateSomeDatas()
    {
        yield return new SomeData();
        yield return new SomeData();
        yield return new SomeData();
    }
}

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ć.


40

yield returnjest 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 po yieldinstrukcji.

Spróbujmy to zrozumieć na przykładzie. W tym przykładzie, odpowiadającemu każdej linii, wspomniałem kolejność, w jakiej przebiega wykonanie.

static void Main(string[] args)
{
    foreach (int fib in Fibs(6))//1, 5
    {
        Console.WriteLine(fib + " ");//4, 10
    }            
}

static IEnumerable<int> Fibs(int fibCount)
{
    for (int i = 0, prevFib = 0, currFib = 1; i < fibCount; i++)//2
    {
        yield return prevFib;//3, 9
        int newFib = prevFib + currFib;//6
        prevFib = currFib;//7
        currFib = newFib;//8
    }
}

Ponadto stan jest zachowywany dla każdego wyliczenia. Załóżmy, że mam inne wywołanie Fibs()metody, a następnie stan zostanie zresetowany.


2
ustaw prevFib = 1 - pierwsza liczba Fibonacciego to „1”, a nie „0”
fubo

31

Intuicyjnie słowo kluczowe zwraca wartość z funkcji bez jej opuszczania, tzn. W twoim przykładzie kodu zwraca bieżącą itemwartość, a następnie wznawia pętlę. Bardziej formalnie, jest on używany przez kompilator do generowania kodu dla iteratora . Iteratory to funkcje zwracające IEnumerableobiekty. MSDN ma kilka artykułów na ich temat.


4
Cóż, mówiąc precyzyjnie, nie wznawia pętli, zatrzymuje ją, dopóki rodzic nie wywoła „iterator.next ()”.
Alex

8
@jitbit Właśnie dlatego użyłem „intuicyjnie” i „bardziej formalnie”.
Konrad Rudolph

31

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:

  • Skalowalność, niezawodność i przewidywalność prawdopodobnie ulegną poprawie, ponieważ liczba rekordów nie wpływa znacząco na wymagania dotyczące zasobów aplikacji.
  • Wydajność i czas reakcji prawdopodobnie wzrosną, ponieważ przetwarzanie może rozpocząć się natychmiast, zamiast czekać na załadowanie całej kolekcji jako pierwszej.
  • Odzyskiwanie i wykorzystanie prawdopodobnie poprawią się, ponieważ aplikację można zatrzymać, uruchomić, przerwać lub zakończyć się niepowodzeniem. Tylko elementy w toku zostaną utracone w porównaniu z pobraniem wszystkich danych, w których faktycznie wykorzystano tylko część wyników.
  • Ciągłe przetwarzanie jest możliwe w środowiskach, w których dodawane są ciągłe strumienie obciążenia.

Oto porównanie najpierw zbuduj kolekcję, taką jak lista, w porównaniu do wykorzystania wydajności.

Przykład listy

    public class ContactListStore : IStore<ContactModel>
    {
        public IEnumerable<ContactModel> GetEnumerator()
        {
            var contacts = new List<ContactModel>();
            Console.WriteLine("ContactListStore: Creating contact 1");
            contacts.Add(new ContactModel() { FirstName = "Bob", LastName = "Blue" });
            Console.WriteLine("ContactListStore: Creating contact 2");
            contacts.Add(new ContactModel() { FirstName = "Jim", LastName = "Green" });
            Console.WriteLine("ContactListStore: Creating contact 3");
            contacts.Add(new ContactModel() { FirstName = "Susan", LastName = "Orange" });
            return contacts;
        }
    }

    static void Main(string[] args)
    {
        var store = new ContactListStore();
        var contacts = store.GetEnumerator();

        Console.WriteLine("Ready to iterate through the collection.");
        Console.ReadLine();
    }

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

public class ContactYieldStore : IStore<ContactModel>
{
    public IEnumerable<ContactModel> GetEnumerator()
    {
        Console.WriteLine("ContactYieldStore: Creating contact 1");
        yield return new ContactModel() { FirstName = "Bob", LastName = "Blue" };
        Console.WriteLine("ContactYieldStore: Creating contact 2");
        yield return new ContactModel() { FirstName = "Jim", LastName = "Green" };
        Console.WriteLine("ContactYieldStore: Creating contact 3");
        yield return new ContactModel() { FirstName = "Susan", LastName = "Orange" };
    }
}

static void Main(string[] args)
{
    var store = new ContactYieldStore();
    var contacts = store.GetEnumerator();

    Console.WriteLine("Ready to iterate through the collection.");
    Console.ReadLine();
}

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.

static void Main(string[] args)
{
    var store = new ContactYieldStore();
    var contacts = store.GetEnumerator();
    Console.WriteLine("Ready to iterate through the collection");
    Console.WriteLine("Hello {0}", contacts.First().FirstName);
    Console.ReadLine();
}

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.


1
Ta odpowiedź wymaga więcej uwagi! Thx
leon22

@ leon22 absolutnie +2
snr

26

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 yieldpomocą 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.


18

Słowo yieldkluczowe umożliwia utworzenie IEnumerable<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 foreachpętli. Jednakże foreachpę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:

IEnumerable<int> IteratorBlock()
{
    Console.WriteLine("Begin");
    yield return 1;
    Console.WriteLine("After 1");
    yield return 2;
    Console.WriteLine("After 2");
    yield return 42;
    Console.WriteLine("End");
}

Rzeczywiste bloki iteratora często mają warunki i pętle, ale kiedy sprawdzasz warunki i rozwijasz pętle, nadal kończą się jako yieldinstrukcje przeplatane z innym kodem.

Do wyliczenia bloku iteratora foreachużywana jest pętla:

foreach (var i in IteratorBlock())
    Console.WriteLine(i);

Oto wynik (tutaj nie ma niespodzianek):

Zaczynać
1
Po 1
2)
Po 2
42
Koniec

Jak wspomniano powyżej, foreachcukier syntaktyczny:

IEnumerator<int> enumerator = null;
try
{
    enumerator = IteratorBlock().GetEnumerator();
    while (enumerator.MoveNext())
    {
        var i = enumerator.Current;
        Console.WriteLine(i);
    }
}
finally
{
    enumerator?.Dispose();
}

Próbując rozwiązać ten problem, stworzyłem schemat sekwencji z usuniętymi abstrakcjami:

Schemat sekwencji bloku iteratora C #

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:

var evenNumbers = IteratorBlock().Where(i => i%2 == 0);

W tym momencie iterator nie wykonał się. WhereKlauzula tworzy nowy IEnumerable<T>, który owija IEnumerable<T>zwrócony przez IteratorBlockale przeliczalna musi jeszcze zostać wymienione. Dzieje się tak, gdy wykonujesz foreachpętlę:

foreach (var evenNumber in evenNumbers)
    Console.WriteLine(eventNumber);

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żyje foreachpętli wyliczyć przeliczalnego. Na przykład ToList()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 jak ToList().


18

Jeśli dobrze to rozumiem, oto jak sformułowałbym to z perspektywy funkcji implementującej IEnumerable z wydajnością.

  • Tutaj jest jeden.
  • Zadzwoń ponownie, jeśli będziesz potrzebować innego.
  • Zapamiętam to, co już ci dałem.
  • Będę wiedział tylko, czy dam ci kolejną, kiedy znów zadzwonisz.

proste i genialne
Harry

10

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.


Najlepsze jak dotąd wyjaśnienie. Czy są to te same generatory w Pythonie?
petrosmm

7

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>.


5

Wytwarza niezliczoną sekwencję. W rzeczywistości tworzy lokalną sekwencję IEnumerable i zwraca ją jako wynik metody


3

Ten link ma prosty przykład

Tutaj są jeszcze prostsze przykłady

public static IEnumerable<int> testYieldb()
{
    for(int i=0;i<3;i++) yield return 4;
}

Zauważ, że zwrot z wydajności nie powróci z metody. Możesz nawet umieścić WriteLinepoyield 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ść, IEnumerablelistę ints, którą zwraca po zakończeniu.

public static IEnumerable<int> testYieldb()
{
    yield return 4;
    console.WriteLine("abc");
    yield return 4;
}

Zauważ też, że kiedy używasz dochodu, to, co zwracasz, nie jest tego samego typu co funkcja. Jest to rodzaj elementu na IEnumerableliście.

Dochodu używasz z typem zwrotu metody jako IEnumerable. Jeśli typem zwracanym przez metodę jest intlub List<int>używasz yield, to nie zostanie skompilowana. Możesz użyć IEnumerablemetody 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.

static void Main(string[] args)
{
    testA();
    Console.Write("try again. the above won't execute any of the function!\n");

    foreach (var x in testA()) { }


    Console.ReadLine();
}



// static List<int> testA()
static IEnumerable<int> testA()
{
    Console.WriteLine("asdfa");
    yield return 1;
    Console.WriteLine("asdf");
}

Uwaga - jeśli próbujesz zrozumieć SelectMany, używa wydajności, a także generycznych .. ten przykład może pomóc public static IEnumerable<TResult> testYieldc<TResult>(TResult t) { yield return t; }i public static IEnumerable<TResult> testYieldc<TResult>(TResult t) { return new List<TResult>(); }
barlop

Wygląda na bardzo dobre wytłumaczenie! To mogła być zaakceptowana odpowiedź.
pongapundit

@pongapundit dzięki, moja odpowiedź jest z pewnością jasna i prosta, ale sama nie wykorzystałam dużo, inni autorzy mają więcej doświadczenia i wiedzę na temat jej zastosowań niż ja. To, co napisałem o wydajności, pochodziło prawdopodobnie od podrapania się w głowę, próbując znaleźć odpowiedzi tutaj i na ten link dotnetperls! Ale ponieważ nie znam yield returntego 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.
barlop

3

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.

        public static IEnumerable<int> CreateCollectionWithList()
        {
            var list =  new List<int>();
            list.Add(10);
            list.Add(0);
            list.Add(1);
            list.Add(2);
            list.Add(20);

            return list;
        }

Przykład: użycie Yield, tj. Lazy Execution.

    public static IEnumerable<int> CreateCollectionWithYield()
    {
        yield return 10;
        for (int i = 0; i < 3; i++) 
        {
            yield return i;
        }

        yield return 20;
    }

Teraz, gdy wywołam obie metody.

var listItems = CreateCollectionWithList();
var yieldedItems = CreateCollectionWithYield();

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.


-3

Próbuje wprowadzić Rubinową Dobroć :)
Koncepcja: Oto przykładowy kod Ruby, który wypisuje każdy element tablicy

 rubyArray = [1,2,3,4,5,6,7,8,9,10]
    rubyArray.each{|x| 
        puts x   # do whatever with x
    }

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.

//calling code
foreach(int i in obCustomClass.Each())
{
    Console.WriteLine(i.ToString());
}

// CustomClass implementation
private int[] data = {1,2,3,4,5,6,7,8,9,10};
public IEnumerable<int> Each()
{
   for(int iLooper=0; iLooper<data.Length; ++iLooper)
        yield return data[iLooper]; 
}

7
-1 Ta odpowiedź nie jest dla mnie odpowiednia. Tak, C # yieldjest sprzężony z IEnumerable, a C # nie ma Rubinowej koncepcji „bloku”. Ale C # ma lambdy, które mogłyby pozwolić na implementację ForEachmetody, podobnie jak Ruby each. Nie oznacza to jednak, że byłoby to dobrym pomysłem .
rsenna

Jeszcze lepiej: public IEnumerable <int> Each () {int index = 0; dane zwrotu wydajności [indeks ++]; }
ata
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.