tags:

views:

524

answers:

4

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?
+21  A: 

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
    });
SLaks
+10  A: 

Your method signature is wrong. It should be:

public static IEnumerable<DesktopComputer> BuildComputerAssets()
Brian Genisio
+4  A: 

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.

Michael Stum
+1  A: 

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).

Tomas Petricek