W C#, dlaczego anonimowa metoda nie może zawierać instrukcji yield?

Pomyślałem, że byłoby miło zrobić coś takiego (z lambdą robiącą zwrot plonu):

public IList<T> Find<T>(Expression<Func<T, bool>> expression) where T : class, new()
{
    IList<T> list = GetList<T>();
    var fun = expression.Compile();

    var items = () => {
        foreach (var item in list)
            if (fun.Invoke(item))
                yield return item; // This is not allowed by C#
    }

    return items.ToList();
}

Jednak okazało się, że nie mogę używać yield w anonimowej metodzie. Zastanawiam się dlaczego. yield docs po prostu powiedzieć, że nie jest dozwolone.

Ponieważ nie było to dozwolone, po prostu utworzyłem listę i dodałem do niej pozycje.

Author: Kenan E. K., 2009-08-01

5 answers

Eric Lippert napisał ostatnio serię postów na blogu o tym, dlaczego w niektórych przypadkach wydajność nie jest dozwolona.

EDIT2:

  • Część 7 (ten został opublikowany później i konkretnie odnosi się do tego pytania)

Prawdopodobnie znajdziesz tam odpowiedź...


EDIT1: jest to wyjaśnione w komentarzach części 5, W odpowiedzi Erica na komentarz Abhijeet Patel:

Q:

Eric,

Czy możesz również podać jakiś wgląd w dlaczego "plony" nie są dozwolone wewnątrz metoda anonimowa lub wyrażenie lambda

A:

Dobre pytanie. Chciałbym mieć anonimowe bloki iteratora. Byłoby całkowicie niesamowite, aby móc budować sobie mały generator sekwencji in-place that closed over local zmienne. The reason why nie jest proste: korzyści nie / align = "left" / The awesomeness of wykonanie generatorów sekwencji na miejscu jest właściwie całkiem mały w wielkim schemat rzeczy i metody nominalne wykonuj pracę wystarczająco dobrze w większości scenariusze. Więc korzyści nie są to fascynujące.

Koszty są duże. Iterator przepisywanie jest najbardziej skomplikowane transformacja w kompilatorze oraz metoda anonimowa przepisywanie jest drugi najbardziej skomplikowany. Anonymous metody mogą być wewnątrz innych anonymous metody, a metody anonimowe mogą być wewnątrz bloków iteratora. Dlatego, to, co robimy, to najpierw przepisujemy wszystkie anonimowych metod, aby stały się metody klasy zamkniętej. To jest the second-last thing the compiler czy przed emisją IL dla metody. Po wykonaniu tego kroku iterator rewriter może zakładać, że nie ma metody anonimowe w iteratorze blok; wszystkie zostały przepisane już. Dlatego iterator rewriter może się skoncentrować na przepisywanie iteratora, bez martwiąc się, że może być niezrealizowana anonimowa metoda.

Ponadto iterator blokuje nigdy " Gniazdo", w przeciwieństwie do metod anonimowych. Iterator rewriter może założyć, że wszystkie Iteratory bloki są "najwyższy poziom".

Jeśli metody anonimowe mogą zawierają bloki iteratora, wówczas oba te założenia wychodzą na jaw. Możesz mieć blok iteratora, który zawiera anonimową metodę, która zawiera anonimową metodę, która zawiera blok iteratora, który zawiera metoda anonimowa, oraz... Fuj. Teraz musimy napisać przepisanie pass, który może obsługiwać zagnieżdżony iterator bloków i zagnieżdżonych anonimowych metod w w tym samym czasie, łącząc nasze dwie najbardziej skomplikowanych algorytmów w jednym Dalekim bardziej skomplikowany algorytm. Byłoby być naprawdę trudne do zaprojektowania, wdrożenia, i test. Jesteśmy na tyle sprytni, by to zrobić. więc, jestem pewien. Mamy Inteligentny Zespół proszę. Ale nie chcemy brać na siebie że duże obciążenie dla " miło mieć ale nie jest to konieczne" funkcja. -- Eric

 114
Author: Thomas Levesque,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2021-01-16 06:15:46

Eric Lippert napisał znakomitą serię artykułów na temat ograniczeń (i decyzji projektowych wpływających na te wybory) na blokach iteratora

W szczególności bloki iteratora są implementowane przez niektóre zaawansowane transformacje kodu kompilatora. Transformacje te oddziaływałyby na transformacje, które zachodzą wewnątrz funkcji anonimowych lub lambda, tak że w pewnych okolicznościach obie próbowałyby "przekształcić" kod w inną konstrukcję, która była niezgodna z innymi.

W rezultacie nie wolno im wchodzić w interakcje.

Jak działają bloki iteratora pod maską, dobrze sobie radzimy tutaj.

Jako prosty przykład niezgodności:

public IList<T> GreaterThan<T>(T t)
{
    IList<T> list = GetList<T>();
    var items = () => {
        foreach (var item in list)
            if (fun.Invoke(item))
                yield return item; // This is not allowed by C#
    }

    return items.ToList();
}

Kompilator chce jednocześnie przekonwertować to na coś w stylu:

// inner class
private class Magic
{
    private T t;
    private IList<T> list;
    private Magic(List<T> list, T t) { this.list = list; this.t = t;}

    public IEnumerable<T> DoIt()
    {
        var items = () => {
            foreach (var item in list)
                if (fun.Invoke(item))
                    yield return item;
        }
    }
}

public IList<T> GreaterThan<T>(T t)
{
    var magic = new Magic(GetList<T>(), t)
    var items = magic.DoIt();
    return items.ToList();
}

I jednocześnie aspekt iteracyjny próbuje wykonać swoją pracę, aby stworzyć małą maszynę stanową. Niektóre proste przykłady mogą działać z dużą ilością sprawdzanie sanity (najpierw zajmowanie się (ewentualnie arbitralnie) zagnieżdżonymi zamknięciami), a następnie sprawdzanie, czy wynikowe klasy dolnego poziomu mogą zostać przekształcone w maszyny stanu iteratora.

Jakkolwiek by to było

    Sporo pracy.
  1. nie mógłby działać we wszystkich przypadkach, gdyby chociaż aspekt bloku iteratora nie był w stanie uniemożliwić aspektowi zamknięcia stosowania pewnych przekształceń dla wydajności (jak promowanie zmiennych lokalnych do instancji zmienne zamiast pełnowartościowej klasy zamknięcia).
    • gdyby istniała nawet niewielka szansa na nakładanie się tam, gdzie niemożliwe lub wystarczająco trudne, aby nie zostać wdrożonym, wówczas liczba problemów związanych z obsługą byłaby prawdopodobnie wysoka, ponieważ subtelna zmiana łamiąca zostałaby utracona przez wielu użytkowników.
  2. można go bardzo łatwo obejść.

W twoim przykładzie tak:

public IList<T> Find<T>(Expression<Func<T, bool>> expression) 
    where T : class, new()
{
    return FindInner(expression).ToList();
}

private IEnumerable<T> FindInner<T>(Expression<Func<T, bool>> expression) 
    where T : class, new()
{
    IList<T> list = GetList<T>();
    var fun = expression.Compile();
    foreach (var item in list)
        if (fun.Invoke(item))
            yield return item;
}
 21
Author: ShuggyCoUk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-09-24 14:17:36

Niestety Nie wiem, dlaczego na to nie pozwolili, ponieważ oczywiście jest całkowicie możliwe, aby wyobrazić sobie, jak to będzie działać.

Jednak metody anonimowe są już częścią "magii kompilatora" w tym sensie, że metoda zostanie wyodrębniona albo do metody w istniejącej klasie, albo nawet do zupełnie nowej klasy, w zależności od tego, czy dotyczy zmiennych lokalnych, czy nie.

Dodatkowo zaimplementowane są również metody iteracyjne wykorzystujące yield przy użyciu kompilatora Magia.

Domyślam się, że jeden z tych dwóch elementów sprawia, że kod nie jest identyfikowalny z drugim kawałkiem magii, i że zdecydowano się nie poświęcać czasu na to, aby to działało dla obecnych wersji kompilatora C#. Oczywiście może to wcale nie być świadomy wybór i że po prostu nie działa, ponieważ nikt nie pomyślał, aby go wdrożyć.

Na 100% trafne pytanie proponuję skorzystać ze strony Microsoft Connect i zgłosić pytanie, jestem pewien, że coś dostaniesz użyteczny w zamian.

 4
Author: Lasse V. Karlsen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-08-01 23:17:15

Zrobiłbym to:

IList<T> list = GetList<T>();
var fun = expression.Compile();

return list.Where(item => fun.Invoke(item)).ToList();
Oczywiście, że potrzebujesz Systemu.Rdzeń.dll odwołuje się z. NET 3.5 Dla metody Linq. And include:
using System.Linq;
Zdrówko, zdrówko]}

Sly

 1
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-09-19 20:42:58

Może to tylko ograniczenie składni. W Visual Basic. NET, który jest bardzo podobny do C#, jest to całkowicie możliwe, podczas gdy niewygodne napisać

Sub Main()
    Console.Write("x: ")
    Dim x = CInt(Console.ReadLine())
    For Each elem In Iterator Function()
                         Dim i = x
                         Do
                             Yield i
                             i += 1
                             x -= 1
                         Loop Until i = x + 20
                     End Function()
        Console.WriteLine($"{elem} to {x}")
    Next
    Console.ReadKey()
End Sub

Zwróć również uwagę na nawiasy ' here; funkcję lambda Iterator Function...End Function zwraca an IEnumerable(Of Integer) ale nie jest takim obiektem. Trzeba go wezwać, aby zdobyć ten przedmiot.

Skonwertowany Kod przez [1] powoduje błędy w C # 7.3 (CS0149):

static void Main()
{
    Console.Write("x: ");
    var x = System.Convert.ToInt32(Console.ReadLine());
    // ERROR: CS0149 - Method name expected 
    foreach (var elem in () =>
    {
        var i = x;
        do
        {
            yield return i;
            i += 1;
            x -= 1;
        }
        while (!i == x + 20);
    }())
        Console.WriteLine($"{elem} to {x}");
    Console.ReadKey();
}

Zdecydowanie nie zgadzam się z uzasadnieniem podanym w innych odpowiedziach, że jest to trudne dla kompilatora do obsługi. Iterator Function() widzisz w VB.NET example jest stworzony specjalnie dla iteratorów lambda.

W VB Znajduje się słowo kluczowe Iterator; nie ma ono odpowiednika w języku C#. IMHO, nie ma prawdziwego powodu, dla którego nie jest to cecha C#.

Więc jeśli naprawdę, naprawdę chcesz anonimowych funkcji iteratora, obecnie używaj Visual Basic lub (nie sprawdzałem tego) F#, jak stwierdzono w komentarzu Część # 7 w odpowiedzi @Thomas Levesque (wykonaj Ctrl+F dla F#).

 0
Author: Bolpat,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-10-08 15:20:33