Waarvoor wordt het rendementszoekwoord gebruikt in C#?

In de Hoe kan ik slechts een fragment van IList blootleggen&lt ;>vraag één van de antwoorden had het volgende codefragment:

IEnumerable<object> FilteredList()
{
    foreach(object item in FullList)
    {
        if(IsItemInPartialList(item))
            yield return item;
    }
}

Wat doet het rendementszoekwoord daar? Ik heb er op een paar plaatsen naar verwezen, en nog een andere vraag, maar ik ben er niet helemaal achter wat het eigenlijk doet. Ik ben gewend aan opbrengst te denken in de zin dat de ene draad toegeeft aan de andere, maar dat lijkt hier niet relevant.


Antwoord 1, autoriteit 100%

Het contextuele zoekwoord yielddoet hier eigenlijk heel veel.

De functie retourneert een object dat de IEnumerable<object>interface implementeert. Als een aanroepende functie foreachover dit object start, wordt de functie opnieuw aangeroepen totdat deze “opbrengt”. Dit is syntactische suiker die is geïntroduceerd in C# 2.0. In eerdere versies moest je je eigen IEnumerableen IEnumeratorobjecten maken om dit soort dingen te doen.

De eenvoudigste manier om code als deze te begrijpen, is door een voorbeeld in te typen, een aantal onderbrekingspunten in te stellen en te kijken wat er gebeurt. Probeer dit voorbeeld eens te doorlopen:

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;
}

Als je het voorbeeld doorloopt, zul je zien dat de eerste aanroep van Integers()1retourneert. De tweede aanroep retourneert 2en de regel yield return 1wordt niet opnieuw uitgevoerd.

Hier is een voorbeeld uit de praktijk:

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);
                }
            }
        }
    }
}

Antwoord 2, autoriteit 48%

Iteratie. Het creëert een toestandsmachine “onder de dekens” die onthoudt waar je was op elke extra cyclus van de functie en vanaf daar verder gaat.


Antwoord 3, autoriteit 30%

Opbrengst heeft twee geweldige toepassingen,

  1. Het helpt om aangepaste iteratie te bieden zonder tijdelijke verzamelingen te maken.

  2. Het helpt om stateful iteratie uit te voeren.
    voer hier de afbeeldingsbeschrijving in

Om bovenstaande twee punten meer demonstratief uit te leggen, heb ik een eenvoudige video gemaakt die je kunt bekijken hier


Antwoord 4, autoriteit 17%

Onlangs publiceerde Raymond Chen ook een interessante serie artikelen over het zoekwoord opbrengst.

Terwijl het nominaal wordt gebruikt voor het eenvoudig implementeren van een iteratorpatroon, maar kan worden gegeneraliseerd naar een toestandsmachine. Het heeft geen zin om Raymond te citeren, het laatste deel linkt ook naar ander gebruik (maar het voorbeeld in Entin’s blog is vooral goed en laat zien hoe je asynchrone veilige code schrijft).


Antwoord 5, autoriteit 15%

Op het eerste gezicht is rendement een .NETsuiker om een ​​IEnumerableterug te geven.

Zonder opbrengst worden alle items van de collectie in één keer gemaakt:

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

Dezelfde code die opbrengst gebruikt, het retourneert item voor item:

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

Het voordeel van het gebruik van opbrengst is dat als de functie die uw gegevens gebruikt alleen het eerste item van de verzameling nodig heeft, de rest van de items niet worden gemaakt.

Met de yield-operator kunnen items worden gemaakt zoals gevraagd. Dat is een goede reden om het te gebruiken.


Antwoord 6, autoriteit 5%

Een lijst- of array-implementatie laadt alle items onmiddellijk, terwijl de yield-implementatie een oplossing voor uitgestelde uitvoering biedt.

In de praktijk is het vaak wenselijk om de minimale hoeveelheid werk die nodig is uit te voeren om het resourceverbruik van een applicatie te verminderen.

We kunnen bijvoorbeeld een applicatie hebben die miljoenen records uit een database verwerkt. De volgende voordelen kunnen worden bereikt wanneer we IEnumerable gebruiken in een pull-gebaseerd model voor uitgestelde uitvoering:

  • Schaalbaarheid, betrouwbaarheid en voorspelbaarheidzullen waarschijnlijk verbeteren, aangezien het aantal records geen significante invloed heeft op de resourcevereisten van de applicatie.
  • Prestaties en reactievermogenzullen waarschijnlijk verbeteren, aangezien de verwerking onmiddellijk kan beginnen in plaats van te wachten tot de hele collectie eerst is geladen.
  • Herstelbaarheid en gebruikzullen waarschijnlijk verbeteren omdat de toepassing kan worden gestopt, gestart, onderbroken of mislukt. Alleen de items die in behandeling zijn, gaan verloren in vergelijking met het vooraf ophalen van alle gegevens waarbij slechts een deel van de resultaten daadwerkelijk werd gebruikt.
  • Continue verwerkingis mogelijk in omgevingen waar constante werklaststromen worden toegevoegd.

Hier is een vergelijking tussen eerst een collectie bouwen, zoals een lijst, en opbrengst gebruiken.

Lijstvoorbeeld

   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();
    }

Console-uitvoer

ContactListStore: contactpersoon aanmaken 1

ContactListStore: contactpersoon aanmaken 2

ContactListStore: contact maken 3

Klaar om door de collectie te bladeren.

Opmerking: de hele verzameling is in het geheugen geladen zonder zelfs maar om één item in de lijst te vragen

Opbrengstvoorbeeld

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();
}

Console-uitvoer

Klaar om door de collectie te bladeren.

Opmerking: de verzameling is helemaal niet uitgevoerd. Dit komt door het “uitgestelde uitvoering” karakter van IEnumerable. Het maken van een item gebeurt alleen als het echt nodig is.

Laten we de collectie opnieuw bellen en het gedrag tegengaan wanneer we het eerste contact in de collectie ophalen.

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();
}

Console-uitvoer

Klaar om door de collectie te bladeren

ContactYieldStore: contactpersoon aanmaken 1

Hallo Bob

Leuk! Alleen het eerste contact werd gelegd toen de klant het item uit de collectie “trok”.


Antwoord 7, autoriteit 5%

yield returnwordt gebruikt met tellers. Bij elke call of yield-verklaring wordt de controle teruggegeven aan de beller, maar het zorgt ervoor dat de status van de callee behouden blijft. Hierdoor gaat de aanroeper, wanneer hij het volgende element opsomt, door met de uitvoering in de callee-methode from-instructie onmiddellijk na de yield-instructie.

Laten we dit proberen te begrijpen met een voorbeeld. In dit voorbeeld, overeenkomend met elke regel, heb ik de volgorde genoemd waarin de uitvoering verloopt.

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

Ook wordt de status bijgehouden voor elke opsomming. Stel dat ik nog een aanroep van de Fibs()-methode heb, dan wordt de status ervoor gereset.


Antwoord 8, autoriteit 4%

Intuïtief retourneert het trefwoord een waarde uit de functie zonder deze te verlaten, d.w.z. in uw codevoorbeeld retourneert het de huidige waarde van itemen hervat vervolgens de lus. Meer formeel wordt het door de compiler gebruikt om code te genereren voor een iterator. Iterators zijn functies die IEnumerableobjecten retourneren. De MSDNheeft verschillende artikelenover hen.


Antwoord 9, autoriteit 3%

Hier is een eenvoudige manier om het concept te begrijpen:
Het basisidee is, als je een collectie wilt waarop je “foreach” kunt gebruiken, maar het verzamelen van de items in de collectie om de een of andere reden duur is (zoals ze uit een database opvragen), EN je heb je vaak niet de hele collectie nodig, dan creëer je een functie die de collectie stuk voor stuk opbouwt en teruggeeft aan de consument (die de ophaalactie dan vroegtijdig kan beëindigen).

Zie het als volgt:je gaat naar de vleesbalie en wilt een pond gesneden ham kopen. De slager neemt een ham van 10 pond naar achteren, legt deze op de snijmachine, snijdt het geheel in plakjes, brengt dan de stapel plakjes naar je toe en meet er een pond van af. (Oude weg).
Met yieldbrengt de slager de snijmachine naar de toonbank, begint elke plak in plakjes te snijden en op de weegschaal te “leveren” tot hij 1 pond meet, wikkelt het dan voor je en je bent klaar. De oude manier is misschien beter voor de slager (laat hem zijn machines organiseren zoals hij wil), maar de nieuwe manier is in de meeste gevallen duidelijk efficiënter voor de consument.


Antwoord 10, autoriteit 3%

Als ik dit goed begrijp, zou ik dit als volgt formuleren vanuit het perspectief van de functie die IEnumerable implementeert met opbrengst.

  • Hier is er een.
  • Bel opnieuw als je er nog een nodig hebt.
  • Ik zal onthouden wat ik je al heb gegeven.
  • Ik weet pas of ik je een andere kan geven als je weer belt.

Antwoord 11, autoriteit 3%

Met het trefwoord yieldkunt u een IEnumerable<T>maken in de vorm op een iteratorblok. Dit iteratorblok ondersteunt uitgestelde uitvoeringen als je niet bekend bent met het concept, kan het bijna magisch lijken. Maar uiteindelijk is het gewoon code die wordt uitgevoerd zonder rare trucjes.

Een iteratorblok kan worden omschreven als syntactische suiker waarbij de compiler een toestandsmachine genereert die bijhoudt hoe ver de opsomming van de opsombare is gevorderd. Om een ​​enumerable op te sommen, gebruik je vaak een foreachlus. Een foreach-lus is echter ook syntactische suiker. Je bent dus twee abstracties verwijderd van de echte code en daarom is het in eerste instantie misschien moeilijk te begrijpen hoe het allemaal samenwerkt.

Stel dat je een heel eenvoudig iteratorblok hebt:

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");
}

Echte iteratorblokken hebben vaak voorwaarden en lussen, maar wanneer u de voorwaarden controleert en de lussen uitrolt, eindigen ze nog steeds als yield-statements verweven met andere code.

Om het iteratorblok op te sommen wordt een foreachlus gebruikt:

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

Hier is de output (geen verrassingen hier):

Begin
1
na 1
2
na 2
42
Einde

Zoals hierboven vermeld is foreachsyntactische suiker:

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

In een poging dit te ontwarren heb ik een sequentiediagram gemaakt met de abstracties verwijderd:

C# iterator blokvolgorde diagram

De statusmachine die door de compiler wordt gegenereerd, implementeert ook de enumerator, maar om het diagram duidelijker te maken, heb ik ze als afzonderlijke instanties getoond. (Als de statusmachine wordt opgesomd vanuit een andere thread, krijg je eigenlijk afzonderlijke instanties, maar dat detail is hier niet belangrijk.)

Elke keer dat u uw iteratorblok aanroept, wordt er een nieuwe instantie van de statusmachine gemaakt. Uw code in het iteratorblok wordt echter niet uitgevoerd totdat enumerator.MoveNext()voor de eerste keer wordt uitgevoerd. Zo werkt uitgesteld uitvoeren. Hier is een (nogal dwaas) voorbeeld:

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

Op dit punt is de iterator niet uitgevoerd. De Where-clausule creëert een nieuwe IEnumerable<T>die de IEnumerable<T>omhult die wordt geretourneerd door IteratorBlock, maar dit opsombaar moet nog worden opgesomd. Dit gebeurt wanneer u een foreach-lus uitvoert:

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

Als u de opsombare twee keer opsomt, wordt elke keer een nieuwe instantie van de toestandsmachine gemaakt en voert uw iteratorblok dezelfde code twee keer uit.

Merk op dat LINQ-methoden zoals ToList(), ToArray(), First(), Count()etc. zal een foreachlus gebruiken om het opsombare op te sommen. ToList()zal bijvoorbeeld alle elementen van de opsombare lijst opsommen en opslaan in een lijst. U hebt nu toegang tot de lijst om alle elementen van de opsombare te krijgen zonder dat het iteratorblok opnieuw wordt uitgevoerd. Er is een afweging tussen het gebruik van CPU om de elementen van de opsomming meerdere keren te produceren en geheugen om de elementen van de opsomming op te slaan om ze meerdere keren te openen bij gebruik van methoden zoals ToList().


Antwoord 12

Het C# yield-sleutelwoord, om het simpel te zeggen, staat veel oproepen toe naar een codelichaam, een iterator genoemd, dat weet hoe het moet terugkeren voordat het klaar is en, wanneer het opnieuw wordt aangeroepen, verder gaat waar het was gebleven – dat wil zeggen het helpt een iterator transparant stateful te worden per item in een volgorde die de iterator in opeenvolgende aanroepen retourneert.

In JavaScript wordt hetzelfde concept Generators genoemd.


Antwoord 13

Een belangrijk punt over het rendementszoekwoord is Lazy Execution. Wat ik bedoel met Lazy Execution is om uit te voeren wanneer dat nodig is. Een betere manier om het te zeggen is door een voorbeeld te geven

Voorbeeld: geen rendement gebruiken, d.w.z. geen luie uitvoering.

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;
}

Voorbeeld: rendement gebruiken, d.w.z. luie uitvoering.

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

Als ik nu beide methoden aanroep.

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

u zult merken dat listItems 5 items zullen bevatten (beweeg uw muis over listItems tijdens het debuggen).
Terwijl yieldItems alleen een verwijzing naar de methode heeft en niet naar de items.
Dat betekent dat het het proces om items in de methode te krijgen niet heeft uitgevoerd. Een zeer efficiënte manier om alleen gegevens te krijgen wanneer dat nodig is.
De daadwerkelijke implementatie van opbrengst is te zien in ORM zoals Entity Framework en NHibernate enz.


Antwoord 14

Het is een zeer eenvoudige en gemakkelijke manier om een ​​opsomming te maken voor uw object. De compiler maakt een klasse die uw methode omhult en die in dit geval IEnumerable<object> implementeert. Zonder het yield-sleutelwoord zou u een object moeten maken dat IEnumerable<object> implementeert.


Antwoord 15

Het produceert een optelbare reeks. Wat het doet, is in feite een lokale IEnumerable-reeks maken en deze retourneren als een methoderesultaat


Antwoord 16

Deze linkheeft een eenvoudig voorbeeld

Hier vindt u nog eenvoudigere voorbeelden

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

Merk op dat het rendement van de opbrengst niet terugkeert van de methode. U kunt zelfs een WriteLineplaatsen na de yield return

Het bovenstaande levert een IEnumerable op van 4 ints 4,4,4,4

Hier met een WriteLine. Zal 4 aan de lijst toevoegen, abc afdrukken, dan 4 aan de lijst toevoegen, dan de methode voltooien en dus echt terugkeren van de methode (zodra de methode is voltooid, zoals zou gebeuren met een procedure zonder een terugkeer). Maar dit zou een waarde hebben, een IEnumerablelijst van ints, die terugkomt bij voltooiing.

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

Merk ook op dat wanneer u yield gebruikt, wat u retourneert, niet van hetzelfde type is als de functie. Het is van het type element in de IEnumerablelijst.

Je gebruikt opbrengst met het retourtype van de methode als IEnumerable. Als het retourtype van de methode intof List<int>is en je gebruikt yield, dan zal deze niet compileren. U kunt het IEnumerable-methoderetourtype gebruiken zonder opbrengst, maar het lijkt erop dat u yield niet kunt gebruiken zonder het IEnumerable-methoderetourtype.

En om het uit te voeren moet je het op een speciale manier aanroepen.

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");
}

Antwoord 17

Het probeert wat Ruby Goodness binnen te brengen 🙂
Concept:dit is een voorbeeld van Ruby-code die elk element van de array afdrukt

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

De implementatie van elke methode van de array geeftcontrole over aan de aanroeper (de ‘puts x’) met elkelement van de array netjes gepresenteerd als x. De beller kan dan doen wat hij moet doen met x.

Echter .Netgaat hier niet helemaal.. C# lijkt opbrengst te hebben gekoppeld aan IEnumerable, op een manier die je dwingt een foreach-lus in de beller te schrijven, zoals te zien is in het antwoord van Mendelt . Iets minder elegant.

//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]; 
}

LEAVE A REPLY

Please enter your comment!
Please enter your name here

13 − 7 =

Other episodes