2010-03-10 8 views
38

Rozważ ten fragment zaciemnionego kodu. Zamiarem jest stworzenie nowego obiektu w locie za pośrednictwem anonimowego konstruktora i yield return go. Celem jest uniknięcie konieczności utrzymywania lokalnej kolekcji tylko po to, by ją po prostu return.C#: zwrot z inwestycji w foreach kończy się niepowodzeniem - ciało nie może być blokiem iteratora

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 
              }; 
    }    
} 

Niestety, bit kodu powoduje wyjątek:

błąd 28 Ciała 'Foo.BuildComputerAssets()' nie może być blok iteracyjnej, ponieważ 'System.Collections.Generic.List' nie jest to typ interfejsu iterator

pytania

  • Co oznacza ten komunikat o błędzie?
  • Jak mogę uniknąć tego błędu i prawidłowo używać yield return?

Odpowiedz

49

można używać tylko yield return w funkcję, która zwraca IEnumerable lub IEnumerator, a nie List<T>.

Należy zmienić funkcję, aby zwrócić wartość IEnumerable<DesktopComputer>.

Alternatywnie, można przepisać funkcja używać List<T>.ConvertAll:

return GetComputerIdTags().ConvertAll(pcTag => 
    new DesktopComputer() { 
     AssetTag = pcTag, 
     Description = "PC " + pcTag, 
     AcquireDate = DateTime.Now 
    }); 
16

Twój podpis metoda jest źle. Powinno być:

public static IEnumerable<DesktopComputer> BuildComputerAssets() 
8

yield działa tylko w rodzajach iterator:

Oświadczenie wydajność może się pojawić tylko wewnątrz bloku iteratora

Iterators są zdefiniowane jako

Typ powrotu iteratora musi być IEnumerable, IEnumerator, IEnume rable <T> lub IEnumerator <T>.

IList i IList <T> temat wdrożenia IEnumerable/IEnumerable <T>, ale każdy dzwoniący do wyliczający oczekuje jednego z czterech typów powyżej i nikt inny.

2

Można również zaimplementować tę samą funkcjonalność przy użyciu kwerendy LINQ (w C# 3.0+). Jest to mniej wydajne niż przy użyciu metody ConvertAll, ale jest bardziej ogólne. Później może być konieczne do korzystania z innych funkcji, takich jak filtrowanie LINQ:

return (from pcTag in GetComputerIdTags() 
     select new DesktopComputer() { 
      AssetTag = pcTag, 
      Description = "PC " + pcTag, 
      AcquireDate = DateTime.Now 
     }).ToList(); 

Sposób ToList konwertuje wynik z IEnumerable<T> do List<T>.Osobiście nie lubię ConvertAll, ponieważ robi to samo co LINQ. Ale ponieważ został dodany wcześniej, nie można go używać z LINQ (powinno być nazywane Select).