Return all enumerables with yield return at once; without looping through
It is something that F# supports with yield!
for a whole collection vs yield
for a single item. (That can be very useful in terms of tail recursion...)
Unfortunately it's not supported in C#.
However, if you have several methods each returning an IEnumerable<ErrorInfo>
, you can use Enumerable.Concat
to make your code simpler:
private static IEnumerable<ErrorInfo> GetErrors(Card card)
{
return GetMoreErrors(card).Concat(GetOtherErrors())
.Concat(GetValidationErrors())
.Concat(AnyMoreErrors())
.Concat(ICantBelieveHowManyErrorsYouHave());
}
There's one very important difference between the two implementations though: this one will call all of the methods immediately, even though it will only use the returned iterators one at a time. Your existing code will wait until it's looped through everything in GetMoreErrors()
before it even asks about the next errors.
Usually this isn't important, but it's worth understanding what's going to happen when.
Yield return results of another Enumerable of the same datatype
You cannot yield return
multiple items. If you want to use iterator methods to concatenate sequences, you'll have to loop through them.
Of course, you could always drop the yield return
completely and construct your IEnumerable<T>
to be returned using other means (LINQ's Concat
method immediately comes to mind).
public IEnumerable<string> Validate(ClassToValidate obj)
{
var subObjectMessages = ValidateSubObject(obj.OtherObjectToValidate);
if (string.IsNullOrEmpty(obj.Name))
{
return new[] { "empty name" }.Concat(subObjectMessages);
}
return subObjectMessages;
}
yield return in custom foreach not working as expected
Deferred execution. The code is executed at the time when you consume the yielded items, eg. by looping over the result or by calling .ToList()
on it. In your code example it seems like you never consume the result of the iteration, so it is never executed.
Breaking apart code using yield return into different methods
In this specific case, the solution is relatively simple: make Wait()
return just the integer:
public IEnumerable Update()
{
while (true)
{
ShootAtPlayer();
yield return Wait(30);
}
}
private int Wait(int x)
{
return removeScript ? -1 : x;
}
In more complicated cases, you can use foreach
, though this makes the syntax much more verbose:
public IEnumerable Update()
{
while (true)
{
ShootAtPlayer();
foreach (var x in Wait(30))
yield return x;
}
}
private IEnumerable Wait(int x)
{
yield return removeScript ? -1 : x;
}
As you can see, you can (ab)use yield return
to implement fibers, but yield return
was never meant for this, so it won't work that well.
What was made for this kind of asynchronous continuations is the new async
-await
. With that, your code could look for example something like this:
public async Task Update()
{
while (true)
{
ShootAtPlayer();
await Wait(30);
}
}
private async Task Wait(int x)
{
await fiber.Wait(removeScript ? -1 : x);
}
As a final note, I think the way you're using removeScript
is not a good idea. End of a script should be signified by the Update()
method actually completing (the enumerable doesn't have any more items, or the Task
completes), not by returning a magic value.
C# yield return enumerator, continue from part the way through
Look at what the IEnumerable<T>
interface actually does.
IEnumerable<T>
is completely stateless; it just exposes a GetEnumerator()
method, which hands back stateful enumerators that keep track of where they're up to.
You need to pass the same enumerator instance between your two loops. foreach
can only operate on IEnumerable<T>
(or similar), so you'll need to replace them with while
loops that use IEnumerator<T>
directly.
Make sure to dispose the IEnumerator
in all codepaths (otherwise, finally
or using
blocks won't run).
Get the first item of an IEnumerable and return the rest as IEnumerable, iterating through only once
Instead of using an out
parameter, you can use ValueTuple<T1, T2>
(as of C# 7.0, documented here) to return two elements: the first item of the IEnumerable<T>
, and the remainder as another IEnumerable<T>
.
using System.Linq;
class Program {
static void Main(string[] args) {
(int first, IEnumerable<int> remainder) = GetFirstAndRemainder(Enumerable.Range(1, 5));
// first = 1
// remainder yields (2, 3, 4, 5)
}
// Returns the first item and the remainders as an IEnumerable
static (T, IEnumerable<T>) GetFirstAndRemainder<T>(IEnumerable<T> sequence) {
var enumerator = sequence.GetEnumerator();
enumerator.MoveNext();
return (enumerator.Current, enumerator.AsEnumerable());
}
}
You also need to convert from an IEnumerator
to an IEnumerable
which I did with an extension method:
static class Extensions {
public static IEnumerable<T> AsEnumerable<T>(this IEnumerator<T> enumerator) {
while (enumerator.MoveNext()) {
yield return enumerator.Current;
}
}
}
Note that due to your requirements, iterating once over the remainder
will exhaust it even though it has the type IEnumerable<T>
.
Related Topics
Deserializing JSON Array into Strongly Typed .Net Object
How to Hide Public Methods from Intellisense
C#: How to Add Subitems in Listview
Combobox.Selectedtext Doesn't Give Me the Selectedtext
Escape Quote in C# for JavaScript Consumption
Authentication with Old Password No Longer Supported, Use 4.1 Style Passwords
How to Ignore Get-Only Properties in JSON.Net Without Using JSONignore Attributes
Cannot Deserialize JSON Array into Type - JSON.Net
Set Environment Variables for a Process
Multiple Identities in ASP.NET Core 2.0
Server.Mappath - Physical Path Given, Virtual Path Expected
Wpf Textblock Memory Leak When Using Font
How to Convert Datetime in Specific Timezone
How to Sort a List<T> by Multiple T.Attributes
How to Add Property-Level Attribute to the Typedescriptor at Runtime