Yield c# là gì

  -  
How lớn use yieldSo how exactly does this work?

C# capabilities keep expanding from year khổng lồ year. New features enrich software development. However, their advantages may not always be so obvious. For example, the good old yield. To some developers, especially beginners, it"s like magic - inexplicable, but intriguing. This article shows how yield works & what this peculiar word hides. Have sầu fun reading!


*

Why you need yield

The yield keywords is used lớn build generators of element sequences. These generators bởi vì not create collections. Instead, the sequence stores the current state - & moves on to the next state on command. Thus, memory requirements are minimal and vày not depend on the number of elements. It"s not hard to guess that generated sequences can be infinite.

Bạn đang xem: Yield c# là gì

In the simplest scenario, the generator stores the current element và contains a set of commands that must be executed lớn get a new element. This is often much more convenient than creating a collection and storing all of its elements.

While there is nothing wrong with writing a class to implement the generator"s behavior, yield simplifies creating such generators significantly. You bởi not have sầu khổng lồ create new classes - everything works already.

I must point out here that yield is not a feature available exclusively in C#. However, while the concept is the same, in different languages yield may be implemented và used differently. Which is why here"s one more reminder that this article talks about yield only in the context of C#.

How to lớn use yield

A standard case

To begin, create a method that generates the sequence you need. The only limitation here is that the method must return one of the following types:

IEnumerableIEnumerableIEnumeratorIEnumerator

Though you can use yield in methods, properties & operators, to simplify this article I"ll nhận xét only methods.

Take a look at this simple yield method:

static IEnumerator GetInts() Console.WriteLine("first"); yield return 1; Console.WriteLine("second"); yield return 2;static void Main() IEnumerator intsEnumerator = GetInts(); // print nothing Console.WriteLine("..."); // print "..." intsEnumerator.MoveNext(); // print "first" Console.WriteLine(intsEnumerator.Current); // print 1 When the GetInts function is called, it returns an object that implements IEnumerator. Then the method exits before it can reach any other code.

The MoveNext method"s first call executes the code inside GetInts - until the first yield return. The value specified in the yield return is assigned to lớn the Current property.

Thus, this code"s first output is "...", then "first", & at the over "1" - a value from the Current property.

The next time you hotline MoveNext again, the method"s execution will pichồng up where it left off. The console will display the "second" message, and 2 will be recorded to the Current property.

Calling MoveNext for the third time will start executing the GetInts method from the moment it was earlier suspended. Since the GetInts method contains no more code, the third MoveNext method gọi will return false. Further MoveNext method"s calls will have no effect and will also return false.

If you điện thoại tư vấn the GetInts method once more, it will return a new object that will allow you lớn start generating new elements.

Local variables, fields, and properties

Local variables initialized inside yield methods, retain their values between MoveNext method calls. For example:

IEnumerator GetNumbers() string stringToPrint = "moveNext"; Console.WriteLine(stringToPrint); // print "moveNext" yield return 0; Console.WriteLine(stringToPrint); // print "moveNext" stringToPrint = "anotherStr"; yield return 1; Console.WriteLine(stringToPrint); // print "anotherStr" If you use the GetNumbers method to lớn create a new generator, the first two times you Điện thoại tư vấn the generator"s MoveNext method, the output will be "moveNext". The MoveNext method"s third gọi will print "anotherStr". This is predictable and logical.

However, working with fields and properties may not be as simple. For example:

string message = "message1";IEnumerator GetNumbers() Console.WriteLine(message); yield return 0; Console.WriteLine(message); yield return 1; Console.WriteLine(message);void Method() var generator = GetNumbers(); generator.MoveNext(); // print "message1" generator.MoveNext(); // print "message1" message = "message2"; generator.MoveNext(); // print "message2" In the code sample above, the GetNumbers method accesses and uses the message field. The field value changes while the sequence is being generated - and this change affects the sequence generation súc tích.

A similar thing happens with properties: if a property value changes, this may affect the generated sequence.

yield break

Aside from yield return, C# offers you another statement - yield break. It allows you to stop sequence generation - that is, exit the generator for good. If the MoveNext method executes yield break, the return is false. No changes lớn fields or properties can make the generator work again. However, if the method that uses yield is called for the second time - it"s a completely different story, because a new object generator is created. That generator would not have encountered yield break.

Let"s take a look at a sample generator that uses yield break:

IEnumerator GenerateMultiplicationTable(int maxValue) for (int i = 2; i maxValue) yield break; yield return result; }} The GenerateMultiplicationTable method multiplies numbers from 2 lớn 10 by each other and returns a sequence that contains the results. If the numbers" hàng hóa exceeds a defined limit (the maxValue parameter), the sequence generation stops. This generator exhibits this behavior thanks to yield break.

Returning IEnumerable

As I mentioned at the beginning, a method that uses yield can return IEnumerable, that is, a sequence itself instead of the sequence"s iterator. An IEnumerable type object often proves to lớn be more convenient, because the IEnumerable interface provides many extension methods, & also supports the foreach loop.

chú ý. If a method"s return type is IEnumerable, the returned object implements both IEnumerableIEnumerator. However, it"s a bad idea to lớn cast an IEnumerable type object to IEnumerator :). Why? I"ll explain later when we get under the hood of this system.

Xem thêm: Phần Mềm Bonjour Là Gì - Cách Xóa Bonjour Trên Máy Tính Đơn Giản Nhất

For now, let"s take a look at this example:

void PrintFibonacci() Console.WriteLine("Fibonacci numbers:"); foreach (int number in GetFibonacci(5)) Console.WriteLine(number); IEnumerable GetFibonacci(int maxValue){ int previous = 0; int current = 1; while (current The GetFibonacci method returns the Fibonacci sequence whose two first elements equal 1. Since the method"s return type is IEnumerable, the PrintFibonacci method can use the foreach loop to traverse the elements inside the sequence.

cảnh báo that each time PrintFibonacci iterates through the IEnumerable sequence, the GetFibonacci function executes from the beginning. Here"s why this happens. The foreach loop uses the GetEnumerator method to lớn traverse elements inside the sequence. Every new GetEnumerator Hotline returns an object that iterates through the sequence elements from the very beginning. For example:

int _rangeStart;int _rangeEnd;void TestIEnumerableYield() IEnumerable polymorphRange = GetRange(); _rangeStart = 0; _rangeEnd = 3; Console.WriteLine(string.Join(" ", polymorphRange)); // 0 1 2 3 _rangeStart = 5; _rangeEnd = 7; Console.WriteLine(string.Join(" ", polymorphRange)); // 5 6 7IEnumerable GetRange(){ for (int i = _rangeStart; i At the string.Join first gọi, the function iterates through the IEnumerable type object for the first time, and as a result the GetRange method is executed. You could achieve sầu a similar result by writing a foreach loop. Then the _rangeStart & _rangeEnd fields are set khổng lồ new values and - behold - we get a different result from iterating through the very same IEnumerable type object!

If you are familiar with LINQ, such behavior may not seem so unusual - after all, the results of LINQ queries are processed the same way. Less experienced developers, however, may be stumped by this phenomenon. Remembering that in some scenartiện ích ios IEnumerable objects & LINQ queries deliver such results will save you a lot of time in the future.

Aside from repeated queries being able khổng lồ produce unexpected results, there is another problem. All operations done khổng lồ initialize elements will be repeated. This can have sầu a negative effect on the application"s performance.

When vị I use yield?

You can use yield everywhere in your tiện ích or nowhere at all. This depends on the particular case and particular project. Aside from the obvious use cases, this construction can help you simulate parallel method execution. The Unity game engine often employs this approach.

As a rule, you bởi not need yield for simple element filtering or to lớn transkhung elements from an existing collection - LINQ can handle this in most cases. However, yield allows you to lớn generate sequences of elements that vày not belong to lớn any collection. For example, when working with a tree, you may need a function that traverses a particular node"s ancestors:

public IEnumerable EnumerateAncestors(SyntaxNode node) while (node != null) node = node.Parent; yield return node; The EnumerateAncestors method allows you to traverse ancestors starting from the closest one. You vì chưng not need lớn create collections, and you can stop element generation at any moment - for example when the function finds a specific ancestor. If you have ideas on how khổng lồ implement this behavior without yield (and your code is at least somewhat concise), I"m always looking forward to lớn your comments below :).

Limitations

Despite its many advantages and possible use cases, the yield statement has a number of limitations related to lớn its internal implementation. I clarified some of them in the next section that explores how the yield statement"s magic works. For now, let"s just take a look at the danh sách of those restrictions:

although the IEnumerator interface contains the Reset method, yield methods return objects that implement the Reset method incorrectly. If you try to lớn điện thoại tư vấn such object"s Reset method, the NotSupportedException exception will be thrown. Be careful with this: bởi not pass a generator object to lớn methods that might call its Reset method;you cannot use yield in anonymous methods or lambda-expressions;you cannot use yield in methods that contain unsafe code;you cannot use the yield return statement inside the try-catch bloông chồng. However, this limitation does not apply to lớn try statements inside try-finally blocks. You can use yield break in try statements inside both try-catchtry-finally blocks.

So how exactly does this work?

Let"s use the dotPeek utility to lớn see what yield statements look like under the hood. Below is the GetFibonacci function that generates the Fibonacci sequence until the maxValue limitation is reached:

IEnumerable GetFibonacci(int maxValue){ int previous = 0; int current = 1; while (current Let"s enable the "Show compiler-generated code" setting và decompile the application with dotPeek. What does the GetFibonacci method really look like?

Well, something lượt thích this:

private IEnumerable GetFibonacci(int maxValue) d__1 getFibonacciD1 = new d__1(-2); getFibonacciD1.4__this = this; getFibonacciD1.3__maxValue = maxValue; return (IEnumerable)getFibonacciD1; Almost nothing like the original method, right? Not lớn mention that the code looks a little strange. Well, let"s take a crachồng at it.

First, we"ll translate the whole thing inkhổng lồ a language we can understvà (no, not IL):

private IEnumerable GetFibonacci(int maxValue) GetFibonacci_generator generator = new GetFibonacci_generator(-2); generator.forThis = this; generator.param_maxValue = maxValue; return generator; This code is the same, but the names are easier on the eyes, and excessive sầu code structures are eliminated. Also, the C# compiler has no problem understanding this code, in comparison to lớn the code listed earlier. This is the code format I use from now on in the article. If you want khổng lồ see what this code looks lượt thích as-is, grab dotPeek (or even better - ildasm) & go ahead :).

This code creates a special object. The object stores a links lớn the current cửa nhà and the maxValue parameter value. "-2" is passed to the constructor - as we see further, this is the generator"s starting state.

The compiler created the generator class automatically, and all the lô ghích we put into lớn the function is implemented there. Now we can take a look at what this class contains.

Let"s start with the declaration:

class GetFibonacci_generator : IEnumerable, IEnumerable, IEnumerator, IEnumerator, IDisposable Nothing unexpected, really... Except for IDisposable that came out of nowhere! It may also seem odd that the class implements IEnumerator, even though the GetFibonacci method returns IEnumerable. Let"s figure out what happened.

Here"s the constructor:

public GetFibonacci_generator(int startState) state = startState; initialThreadId = Environment.CurrentManagedThreadId; The state field stores the "-2" startState value passed khổng lồ the generator at the initialization. The initialThreadId field stores the ID of the thread where the object was created. I"ll explain the purpose of these fields later. Now let"s take a look at the GetEnumerator implementation:

IEnumerator IEnumerable.GetEnumerator() GetFibonacci_generator generator; if (state == -2 &và initialThreadId == Environment.CurrentManagedThreadId) state = 0; generator = this; else generator = new GetFibonacci_generator(0); generator.forThis = forThis; generator.local_maxValue = param_maxValue; return generator; See how when certain conditions are met, the method returns the same object instead of a new one? This peculiarity might seem quite unexpected. The following code fragment confirms it:

IEnumerable enumerable = prog.GetFibonacci(5);IEnumerator enumerator = enumerable.GetEnumerator();Console.WriteLine(enumerable == enumerator); This code"s output is "True". Who would have sầu thought? :)

At the GetEnumerator method Hotline, the returned object"s state field is assigned lớn "0". This is an important step.

After the conditional statement, another meaningful assignment takes place:

generator.local_maxValue = param_maxValue Take another look at the GetFibonacci method (or, to lớn be exact, at what the compiler transformed it into). See how the maxValue parameter is recorded inkhổng lồ the param_maxValue field? It is also recorded to the local_maxValue field.

At first glance, it may seem unclear why the generator uses two fields - param_maxValuelocal_maxValue - lớn store the maxValue parameter. I"ll clarify the mechanics of this further on in this article. Right now, let"s take a look at the MoveNext method:

bool IEnumerator.MoveNext() switch (state) case 0: state = -1; local_previous = 0; local_current = 1; break; case 1: state = -1; local_newCurrent = local_previous + local_current; local_previous = local_current; local_current = local_newCurrent; break; default: return false; if (local_current > local_maxValue) return false; _current = local_current; state = 1; return true; This method implements all lô ghích we programmed into the GetFibonacci method. Before MoveNext exits, it writes the current result inkhổng lồ the _current field. This is the value we get when we access the sequence generator"s Current property.

If the sequence generation must be stopped (in this case when local_current > local_maxValue), the generator"s state remains equal khổng lồ "-1". When the generator"s state field value is "-1", the generator exits - MoveNext does not bởi anything & returns false.

lưu ý that when MoveNext returns false, the _current field value (as well as the Current property value) remains unchanged.

Tricks with type casting

Previously we discussed that when you create a new generator, the "-2" value is recorded to the state field. But take a look at the code. If state = -2, then MoveNext does not perform any actions and returns false. Essentially, the generator does not work. Luckily, the GetEnumerator method Hotline replaces the -2 state with 0. What about calling MoveNext without calling GetEnumerator? Is this possible?

The GetFibonacci method"s return type is IEnumerable, thus, there is no access to lớn the MoveNext method. Nevertheless, the returned object implements both IEnumerable & IEnumerator - so you can use type casting. In this case the developer does not need GetEnumerator and can Call the generator"s MoveNext. However, all calls will return false. Thus, though you may be able lớn "cheat" the system, this hardly benefits you in any way.

Conclusion. When a yield method returns an IEnumerable type object, this object implements both IEnumerableIEnumerator. Casting this object khổng lồ IEnumerator produces a generator that is useless until the GetEnumerator method is called. At the same time, if a generator seems "dead", it may suddenly start working after the GetEnumerator method Điện thoại tư vấn. The code below demonstrates this behavior:

IEnumerable enumerable = GetFibonacci(5);IEnumerator deadEnumerator = (IEnumerator)enumerable;for (int i = 0; i enumerator = enumerable.GetEnumerator();Console.WriteLine(deadEnumerator == enumerator);for (int i = 0; i What bởi vì you think the console will display after the code above sầu is executed? Hint: The code produces the Fibonacci sequence"s first five sầu elements - 1, 1, 2, 3, 5.

We have just reviewed a case of casting to lớn IEnumerator. Is it possible khổng lồ play around with casting to lớn IEnumerable?

Obviously, an object returned by GetEnumerator"s first hotline can be cast to IEnumerable & will work as expected. Take a look at this example:

IEnumerable enumerable = GetInts(0); IEnumerator firstEnumerator = enumerable.GetEnumerator();IEnumerable firstConverted = (IEnumerable)firstEnumerator;Console.WriteLine(enumerable == firstEnumerator);Console.WriteLine(firstConverted == firstEnumerator);Console.WriteLine(firstConverted == enumerable); This code above prints three "True" entries in the console window, because all three references point lớn the same object. Here, casting does not bring any surprises, và will produce a links khổng lồ an existing (và, therefore, correctly working) object.

What about a different scenario? For example, GetEnumerator is called for the second time or in a different thread - và the value it returns is cast to lớn IEnumerable. Take a look at this sample yield method:

IEnumerable RepeatLowerString(string someString) someString.ToLower(); while (true) yield return someString; At a first glance the RepeatLowerString method receives a string as a parameter, converts it khổng lồ lowercase & returns it indefinitely.

Have sầu you noticed something odd in the code above? The RepeatLowerString method, opposite lớn what you may expect, generates a sequence of references lớn the unchanged someString string.

This happens because the ToLower method creates a new string và does not modify the original string. It is not too important in our case, but in real software such mistakes lead to lớn sad consequences and they are worth fighting against. An incorrect ToLower method Điện thoại tư vấn may not seem significant. However, sometimes a function is called incorrectly somewhere in a large pile of code - and that error is almost impossible khổng lồ track down.

Xem thêm: Tư Vấn Cấu Hình Máy Tính 15 Triệu Ngon, Tư Vấn Cấu Hình Máy Tính 15 Triệu

If the project is large, its developers often use a static code analyzer. A static code analyzer is an application that can quickly detect many code bugs. For example, a static code analyzer could scan the RepeatLowerString method và find that error I described earlier. However, the analyzer is definitely not limited lớn detecting "meaningless calls" - it covers an extensive menu of problems.

I recommkết thúc that you use a static analyzer on your projects. The hufa.edu.vn tool is a good choice. It checks projects written in C#, C, C++, and Java & detects a wide variety of problems in source code. Interested? You can read more about hufa.edu.vn on its official trang web and get the analyzer"s không lấy phí trial version.

Meanwhile, I fixed the RepeatLowerString method:

IEnumerable RepeatLowerString(string someString) string lower = someString.ToLower(); while (true) yield return lower; Now let"s experiment with casting to IEnumerable:

IEnumerable enumerable = RepeatLowerString("MyString");IEnumerator firstEnumerator = enumerable.GetEnumerator();IEnumerator secondEnumerator = enumerable.GetEnumerator();var secondConverted = (IEnumerable)secondEnumerator;var magicEnumerator = secondConverted.GetEnumerator();for (int i = 0; i What will the console display after this code is executed?