Wieso benutzt man IEnumerator?
Ich habe mir gerade ein Tutorial zum einfaden von Objekten angeschaut und mich gefragt, wieso der Code so sein muss, wie er ist.
Er sieht folgendermaßen aus:
...
IEnumarator FadeOut()
{
for (float ft = 1f; ft >= 0.1; ft -= 0.1)
{
Color c = renderer.material.color;
c.a = ft;
renderer.material.color = c;
yield return new WaitForSeconds(0.05f);
}
public void startFadeOut()
{
StartCoroutine("FadeOut");
}
Viele Fragen:
- Wieso benutzt man IEnumerator und nicht eine normale Methode und wieso wird diese nicht von Beginn aus ausgeführt, aber die startFadeOut doch?
- Was ist yield return und wieso benutzt man es?
Danke für Antworten. ❤
2 Antworten
Was ist ' yield return'
Kontextabhängiges yield-Schlüsselwort: C#-Referenz | Microsoft Docs
IEnumerable bedeutet, das Objekt kann IEnumerator zurück liefern. Das IEnumerator ist eine Art Iterator-Pattern. Man bekommt nicht eine Liste von Objekte zurück, sondern ein Objekt, was das jeweils nächste Objekt holen kann.
Für z.B. Datenbank-Operationen ist das besonders praktisch. Man führt die DB-Abfrage aus und bekommt z.B. 10 Datensätze zurück, die liegen dann im RAM. Das Enumerator-Objekt holt jedes mal das nächste Element (erst das Erste, dann das Zweite, und so weiter), was aber für die ersten 10 Datensätze eigentlich nur bedeutet, einen Index weiter zu zählen. Bis man dann den 11. Datensatz haben will, denn dann wird nicht nur ein Index hochgezählt, sondern es werden die nächsten 10 Datensätze von der Datenbank abgerufen - und die Datenbank hatte bis dahin auch Zeit, die nächsten 10 Datensätze zu suchen.
Etwas ähnliches mache ich gerade mit einem USB-Gerät, das schickt in Abständen Daten und meine Implementierung liefert ein Enumerable, was mit jedem MoveNext() den jeweils nächsten Datensatz holt oder wartet, bis er da ist.
Tatsächlich ist es das, was die foreach-Schleife macht, sie nutzt IEnumerable um ein IEnumerator-Objekt abzurufen und nach jedem Durchlauf wird MoveNext() aufgerufen, um das jeweils nächste Objekt zu liefern.
So ein Enumerator kann aber ziemlich nervig zu entwickeln sein, das yield return macht es um einiges einfacher, weil der Compiler dann eine StateMachine drum herum generieren kann, die alles nötige implementiert.
Schau dir die Doku an und debugge einfach mal durch, dann siehst Du, wie es sich verhält.
wieso der Code so sein muss wie er ist
Muss er nicht.
Ich habe keine Ahnung von Unity, aber bei normalen Business-Programmen verwendet man normalerweise den generischen IEnumerator. Außerdem nutzt man es, um als Item ein tatsächliches Ergebnis zurück zu liefern und nicht, um zu warten. In normalen Business-Programmen nutzt man für sowas das Task asynchronous programming model oder andere Konzepte.
Oder es ist ein Unity-spezifisches Konzept, aber dann musst Du die Unity-Doku lesen und der Enumerator ist dann nur ein Werkzeug für ein ganz anderes Konzept.
Aber keine Sorge ich bin nicht dumm nur jung🙂
brauche es schlussendlich auch nicht mehr
Das ist ein Irrglaube.
Das ganze Thema ist ein sehr tief in .NET vorhandenes Konzept, quasi überall wird es genutzt und sobald Du die foreach-Schleife nutzt, nutzt Du es auch.
Wenn Du z.B. mit Datenbanken (mit einem ORM, die bieten meist einen LINQ-Provider) arbeitest, dann ist dieses Thema DAS große Konzept, die ganze Technologie baut darauf auf. Man kann so eine LINQ-Abfrage zwar normal in einer foreach-Schleife nutzen, aber gleichzeitig kann man sich auch große Performance-Probleme (und andere Probleme) einbauen, die dann erst irgendwann später auffallen, nur nicht da, wo der eigentliche Code steht.
Alle dieser Fälle, die ich bisher gesehen habe, waren auf mangelndes Verständnis zurückzuführen.
Aber ich gebe zu: Ohne OOP und was eine Abstraktion ist, wirklich verinnerlicht zu haben, ist es schwer, so ein Konzept zu verstehen.
War auch nur so gemeint in diesem Stück Code, aber brauche es doch. Hattest also Recht.
Es handelt sich dabei um eine Schleife mit verzögerter Ausführung.
Wenn du FadeOut aufrust um das IEnumerator Objekt zu erhalten wird die Schleife nicht sofort ausgeführt sondern erst dann wenn man lesend auf das IEnumerator Objekt zugreift.
Das hat den Vorteil, dass bestimmte Methoden einfach Effizienter gestaltet werden können.
Wenn du zB das IEnumerator Objekt in einer foreach Schleife verwendest macht das der Kompiler im wesentlich nur zu einer einzigen Schleife nämlich der for Schleife di in dieser Funktion steht.
Würdest du hingegen ein Array oder eine Liste zurückgeben würde zuerst diese for Schleife durchlaufen und erst danach nochmal die foreach Schleife wobei du in diesem Fall eine Schleifenausführung mehr hast.
Wenn du yield return verwendest musst du zudem immer ein IEnumerator zurückgeben. Das yield return bedeutet nämlich nur wenn das IEnumerator nach einem Wert gefragt wird gib diesen Wert zurück. Das normale Return bedeutet hingegen dass der Wert sofort zurückgegeben wird.
Danke, habe zwar beim ersten Durchlesen nur die Hälfte verstanden, aber ich denke das wird morgen <3
https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/yield
hier ist das noch zum näher durchlesen.
Im wesentlichen entspricht das IEnumerable nichts anderem als Iteratoren die du vielleicht aus anderen Programmiersprachen kennst.
Also ich hab jetzt ein Problem und zwar wird die IEnumarator Methode aufgerufen ohne dass sie das soll. Bei einer Normalen Methode ist dies nicht der Fall, doch wenn ich die benutze verhlt sich eine while-Schleife anders. Kann ich es verhindern dass diese aufgerufen wird? Wie?
Was bedeutet sie wird aufgerufen ohne dass sie das soll?
Wann und wo wird diese Aufgerufen und wie stellst du fest, dass diese Aufgerufen wird.
Die Methode selbst die das IEnumerable liefert sollt nur beim Call aufgrufen werden. Der Iterationsblock selbst also das mit dem yield return wird natürlich bei jedem Aufruf einer Methode des Iterators aufgerufen, das ist normal und auch so gewollt.
Hat sich geklärt. Aber danke fürs Antworten <3
Wie kann man eine While-Schleife in einer Coroutine verzögern, ohne dass sie die "Rechnungen in ihr schon ausführt?
Ich habe eine While-Schleife in einer Coroutine und wenn ich diese mit
'yield return new WaitForSeconds(delay)'
verzögere, passiert das was über eine oder mehrere Sekunden verteilt sein sollte, in einem Frame. Wie kann ich das verhinder?
Also ich kenne die WaitForSeconds Methode nicht aber was liefert die zurück?
Ein yield return bei einem Wait macht imho keinen wirklichen Sinn, weil das WaitForSeconds ja nicht sofort ausgeführt wird sondern weil es erst dann instanziert wird wenn über den Iterator iterriert wird.
Danke, hab zwar nur die Hälfte verstanden (und probier nicht es mir zu erklären, das kommt mit der Zeit) und brauche es schlussendlich auch nicht mehr, aber sehr vielen Dank.