2012-03-09 20 views
17

Dlaczego nie możemy użyć zarówno zwrotu, jak i zwrotu z inwestycji w ten sam sposób?Dlaczego nie można użyć zwrotu "powrót" i "zwrotu plonu" w tej samej metodzie?

Na przykład możemy mieć GetIntegers1 i GetIntegers2 poniżej, ale nie GetIntegers3.

public IEnumerable<int> GetIntegers1() 
{ 
    return new[] { 4, 5, 6 }; 
} 

public IEnumerable<int> GetIntegers2() 
{ 
    yield return 1; 
    yield return 2; 
    yield return 3; 
} 

public IEnumerable<int> GetIntegers3() 
{ 
    if (someCondition) 
    { 
    return new[] {4, 5, 6}; // compiler error 
    } 
    else 
    { 
    yield return 1; 
    yield return 2; 
    yield return 3; 
    } 
} 
+11

poczekaj chwilę, jon skeet przyjdzie teraz. – Juvanis

+0

Dodam, że jeśli naprawdę tego potrzebujesz, możesz utworzyć GetIngegers4, który wywoła GetIntegers1 OR GetIntegers2 w zależności od warunku. – xanatos

+0

Jest to prawdopodobnie oczywiste, ale w takich przypadkach zawsze można rozwinąć swoją kolekcję i zwrot plonów: foreach (pozycja w nowym [] {4,5,6}) zwrot z inwestycji; – Foo42

Odpowiedz

16

return jest gorliwy. Zwraca cały zestaw wyników naraz. yield return buduje moduł wyliczający. Za kulisami kompilator C# emituje wymaganą klasę dla modułu wyliczającego, gdy używasz yield return. Kompilator nie szuka warunków środowiska wykonawczego, takich jak if (someCondition) podczas określania, czy powinien on emitować kod dla wyliczalnego, czy też mieć metodę zwracającą prostą tablicę. Wykrywa, że ​​w używanej przez ciebie metodzie używasz obu, co nie jest możliwe, ponieważ nie może on wysyłać kodu dla modułu wyliczającego, a jednocześnie mieć metodę zwracającą normalną tablicę i wszystko to dla tej samej metody.

+0

Kiedy próbuję debugować kod, widzę, że porusza on wszystkie wiersze kodu w iteracji. W takim razie możemy powiedzieć, że wewnętrznie wbudowuje moduł wyliczający i zwraca go tylko raz ... – Karan

7

Kompilator ponownie zapisuje dowolne metody za pomocą instrukcji yield (return lub break). Obecnie nie może obsłużyć metod, które mogą, ale nie muszą, yield.

Polecam przeczytanie rozdziału 6 książki Jon Skeeta: C# in Depth, której rozdział 6 jest dostępny za darmo - obejmuje całkiem ładnie bloki iteracyjne.

Nie widzę powodu, dla którego nie byłoby to możliwe w przyszłych wersjach kompilatora C#. Inne języki .Net obsługują coś podobnego w postaci operatora 'yield from' (See F# yield!). Jeżeli taki podmiot istnieje w C# to pozwalają, aby napisać kod w postaci:

public IEnumerable<int> GetIntegers() 
{ 
    if (someCondition) 
    { 
    yield! return new[] {4, 5, 6}; 
    } 
    else 
    { 
    yield return 1; 
    yield return 2; 
    yield return 3; 
    } 
} 
10

Nie, nie mogę tego zrobić - blok iterator (coś z yield) nie może użyć regularne (brak wydajności) return. Zamiast tego trzeba użyć 2 metody:

public IEnumerable<int> GetIntegers3() 
{ 
    if (someCondition) 
    { 
    return new[] {4, 5, 6}; // compiler error 
    } 
    else 
    { 
    return GetIntegers3Deferred(); 
    } 
} 
private IEnumerable<int> GetIntegers3Deferred() 
{ 
    yield return 1; 
    yield return 2; 
    yield return 3; 
} 

lub od w tym konkretnym przypadku kod dla obu istnieje już w innych 2 metod:

public IEnumerable<int> GetIntegers3() 
{ 
    return (someCondition) ? GetIntegers1() : GetIntegers2(); 
} 
3

Teoretycznie myślę, że nie ma powodu, dlaczego zwrot i zwrot plonu nie mogą być mieszane: Kompilatorowi łatwo byłoby najpierw przekształcić syntaktycznie każde zdanie return (blabla()); w:

var myEnumerable = blabla(); 
foreach (var m in myEnumerable) 
    yield return m; 
yield break; 

, a następnie kontynuować (przekształcić całą metodę w ... niezależnie od tego, jak ją teraz przekształcają; wewnętrzna anonimowy klasy IEnumerator ?!)

Więc dlaczego nie wybrać się do jego realizacji, tu są dwa przypuszczenia:

  • mogliby postanowił byłoby mylące dla użytkowników, aby mieć zarówno zwrot i powrót plonu na raz,

  • Zwrócenie całego zbioru jest szybsze i tańsze, ale także chętne; budowanie za pomocą zwrotu z zysków jest trochę droższe (szczególnie jeśli jest nazywane rekursywnie, patrz ostrzeżenie Erica Lipperta o przemierzaniu drzew binarnych z deklaracjami zwrotu plonów tutaj: https://stackoverflow.com/a/3970171/671084 ), ale leniwym. Tak więc użytkownik zazwyczaj nie chce mieszać tych elementów: jeśli nie potrzebujesz lenistwa (tj.znasz całą sekwencję) nie ponosisz kary za wydajność, po prostu użyj normalnej metody. Mogli chcieć zmusić użytkownika do myślenia w tym kierunku.

Z drugiej strony wydaje się, że są sytuacje, w których użytkownik mógłby korzystać z niektórych rozszerzeń syntaktycznych; możesz przeczytać to pytanie i odpowiedzi jako przykład (nie to samo pytanie, ale prawdopodobnie z podobnym motywem): Yield Return Many?

+0

To jest właściwie odpowiedź na pytanie "Jakie były powody, dla których Microsoft zdecydował się nie wspierać ...", co byłoby lepszym pytanie w pierwszej kolejności. –

0

Myślę, że głównym powodem, dla którego to nie działa, jest projektowanie w taki sposób, nie jest zbyt skomplikowany, ale jednocześnie jest trudny, z relatywnie niewielką korzyścią.

Co dokładnie będzie robić Twój kod? Czy bezpośrednio zwróci tablicę, czy też ją przerobi?

Jeśli bezpośrednio zwróci tablicę, trzeba będzie wymyślić skomplikowane reguły, na jakich warunkach można zezwolić na return, ponieważ return po yield return nie ma sensu. Prawdopodobnie będziesz musiał wygenerować skomplikowany kod, aby zdecydować, czy metoda zwróci niestandardowy iterator czy tablicę.

Jeśli chcesz powtórzyć kolekcję, prawdopodobnie potrzebujesz lepszego słowa kluczowego. Something like yield foreach. To było faktycznie brane pod uwagę, ale ostatecznie nie zostało wdrożone. Myślę, że pamiętam, że głównym powodem jest to, że naprawdę ciężko jest sprawić, żeby działał dobrze, jeśli masz kilka zagnieżdżonych iteratorów.

+1

Ciężko jest sprawić, aby działał dobrze z zagnieżdżonymi iteratorami, ale ten problem można rozwiązać z pewną sprytnością; zrobili to w C-Omega. Jeśli jednak to zrobisz, zaczniesz napotykać problemy z poprawnością, gdy te zagnieżdżone iteratory zawierają obsługę wyjątków. Jest to piękny pomysł na funkcję i chciałbym, żebyśmy mogli to zrobić, ale współczynnik bólu do wzmocnienia jest zbyt wysoki. –