C#: yield return within a foreach fails - body cannot be an iterator block
Question
Consider this bit of obfuscated code. The intention is to create a new object on the fly via the anonymous constructor and yield return
it. The goal is to avoid having to maintain a local collection just to simply return
it.
public static List<DesktopComputer> BuildComputerAssets()
{
List<string> idTags = GetComputerIdTags();
foreach (var pcTag in idTags)
{
yield return new DesktopComputer() {AssetTag= pcTag
, Description = "PC " + pcTag
, AcquireDate = DateTime.Now
};
}
}
Unfortunately, this bit of code produces an exception:
Error 28 The body of 'Foo.BuildComputerAssets()' cannot be an iterator block because 'System.Collections.Generic.List' is not an iterator interface type
Questions
- What does this error message mean?
- How can I avoid this error and use
yield return
properly?
Solution
You can only use yield return
in a function that returns an IEnumerable
or an IEnumerator
, not a List<T>
.
You need to change your function to return an IEnumerable<DesktopComputer>
.
Alternatively, you can rewrite the function to use List<T>.ConvertAll
:
return GetComputerIdTags().ConvertAll(pcTag =>
new DesktopComputer() {
AssetTag = pcTag,
Description = "PC " + pcTag,
AcquireDate = DateTime.Now
});
OTHER TIPS
Your method signature is wrong. It should be:
public static IEnumerable<DesktopComputer> BuildComputerAssets()
yield only works on Iterator types:
The yield statement can only appear inside an iterator block
Iterators are defined as
The return type of an iterator must be IEnumerable, IEnumerator, IEnumerable<T>, or IEnumerator<T>.
IList and IList<T> do implement IEnumerable/IEnumerable<T>, but every caller to an enumerator expects one of the four types above and none else.
You could also implement the same functionality using a LINQ query (in C# 3.0+). This is less efficient than using ConvertAll
method, but it is more general. Later, you may also need to use other LINQ features such as filtering:
return (from pcTag in GetComputerIdTags()
select new DesktopComputer() {
AssetTag = pcTag,
Description = "PC " + pcTag,
AcquireDate = DateTime.Now
}).ToList();
The ToList
method converts the result from IEnumerable<T>
to List<T>
. I personally don't like ConvertAll
, because it does the same thing as LINQ. But because it was added earlier, it cannot be used with LINQ (it should have been called Select
).