Pomyślałem, że fajnie byłoby zrobić coś takiego (z lambdą zwracającą wydajność):
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();
}
Okazało się jednak, że nie mogę używać wydajności w metodzie anonimowej. Zastanawiam się, dlaczego. W docs plon tylko powiedzieć, że nie jest dozwolone.
Ponieważ nie było to dozwolone, po prostu utworzyłem Listę i dodałem do niej elementy.
c#
yield
anonymous-methods
yield-return
Lance Fisher
źródło
źródło
async
lambdy pozwalająceawait
wewnątrz w C # 5.0, chciałbym wiedzieć, dlaczego nadal nie zaimplementowano anonimowych iteratorów zyield
inside. Mniej więcej jest to ten sam generator maszyny stanów.Odpowiedzi:
Eric Lippert napisał niedawno serię postów na blogu o tym, dlaczego w niektórych przypadkach zbiory są niedozwolone.
EDYCJA2:
Prawdopodobnie znajdziesz tam odpowiedź ...
EDIT1: wyjaśniono to w komentarzach do części 5, w odpowiedzi Erica na komentarz Abhijeeta Patela:
P:
A:
źródło
Eric Lippert napisał doskonałą serię artykułów na temat ograniczeń (i decyzji projektowych wpływających na te wybory) w blokach iteratorów
W szczególności bloki iteratora są implementowane przez pewne wyrafinowane transformacje kodu kompilatora. Przekształcenia te miałyby wpływ na transformacje, które mają miejsce w anonimowych funkcjach lub lambdach, tak że w pewnych okolicznościach obie próbowałyby „przekonwertować” kod na inną konstrukcję, która byłaby niezgodna z drugą.
W rezultacie zabrania się im interakcji.
Sposób działania bloków iteratora pod maską jest tutaj dobrze rozwiązany .
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 jednocześnie chce przekonwertować to na coś takiego:
// 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(); }
a jednocześnie aspekt iteratora próbuje wykonać swoją pracę, aby stworzyć małą maszynę stanów. Niektóre proste przykłady mogą działać z dużą ilością sprawdzania poprawności (najpierw zajmując się (prawdopodobnie arbitralnie) zagnieżdżonymi zamknięciami), a następnie sprawdzaniem, czy klasy wynikowe z najniższego poziomu można przekształcić w iteratorowe maszyny stanu.
Jednak tak by się stało
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; }
źródło
Magic
klasa powinna byćMagic<T>
.Niestety nie wiem, dlaczego na to nie pozwolili, ponieważ oczywiście można sobie wyobrazić, jak to zadziała.
Jednak metody anonimowe są już kawałkiem „magii kompilatora” w tym sensie, że metoda zostanie wyodrębniona albo do metody w istniejącej klasie, albo nawet do całej nowej klasy, w zależności od tego, czy ma do czynienia ze zmiennymi lokalnymi, czy nie.
Ponadto metody iteratora używające
yield
są również implementowane przy użyciu magii kompilatora.Domyślam się, że jeden z tych dwóch sprawia, że kod nie jest identyfikowalny dla drugiego elementu magii i że postanowiono nie tracić czasu na tworzenie tej pracy dla bieżących wersji kompilatora C #. Oczywiście może to wcale nie być świadomy wybór i po prostu nie działa, ponieważ nikt nie pomyślał o jego wdrożeniu.
Aby uzyskać w 100% dokładne pytanie, sugerowałbym skorzystanie z witryny Microsoft Connect i zgłoszenie pytania, jestem pewien, że w zamian otrzymasz coś użytecznego.
źródło
Zrobiłbym to:
IList<T> list = GetList<T>(); var fun = expression.Compile(); return list.Where(item => fun.Invoke(item)).ToList();
Oczywiście do metody Linq potrzebny jest plik System.Core.dll, do którego odwołuje się .NET 3.5. I obejmują:
using System.Linq;
Twoje zdrowie,
Chytry
źródło
Może to tylko ograniczenie składni. W Visual Basic .NET, który jest bardzo podobny do C #, jest to całkowicie możliwe, a jednocześnie niewygodne do pisania
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óć także uwagę na nawiasy
' here
; funkcja lambdaIterator Function
...End Function
zwraca obiekt ,IEnumerable(Of Integer)
ale sam nie jest takim obiektem. Musi zostać wywołany, aby uzyskać ten obiekt.Przekonwertowany kod przez [1] wywołuje 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 powodem podanym w innych odpowiedziach, że kompilator ma trudności z obsługą. Element,
Iterator Function()
który widzisz w przykładzie VB.NET, jest specjalnie utworzony dla iteratorów lambda.W VB jest
Iterator
słowo kluczowe; nie ma odpowiednika w C #. IMHO, nie ma prawdziwego powodu, dla którego nie jest to funkcja 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 do części 7 w odpowiedzi @Thomas Levesque (wykonaj Ctrl + F dla F #).
źródło