Dlaczego używasz wyrażenia > zamiast Func?

Rozumiem lambdy i delegatów Func i Action. Ale wyrażenia mnie pieką. W jakich okolicznościach użyłbyś Expression<Func<T>> zamiast zwykłego starego Func<T>?

Author: Mehrdad Afshari, 2009-04-27

9 answers

Kiedy chcesz traktować wyrażenia lambda jako drzewa wyrażeń i zajrzeć do ich wnętrza zamiast je wykonywać. Na przykład, LINQ to SQL pobiera wyrażenie i konwertuje je na równoważne polecenie SQL i przesyła je do serwera (zamiast wykonywać lambda).

Koncepcyjnie, Expression<Func<T>> jest zupełnie inne od Func<T>. Func<T> oznacza delegate, który jest w zasadzie wskaźnikiem do metody i Expression<Func<T>>oznacza strukturę danych drzewa dla wyrażenia lambda. To struktura drzewa opisuje, co robi wyrażenie lambda, a nie robi to, co jest rzeczywiste. Zasadniczo przechowuje dane o składzie wyrażeń, zmiennych, wywołań metod, ... (na przykład przechowuje informacje takie jak ta lambda jest jakąś stałą + jakiś parametr). Możesz użyć tego opisu, aby przekonwertować go do rzeczywistej metody (z Expression.Compile) lub zrobić z nim inne rzeczy (jak przykład LINQ do SQL). Akt traktowania lambd jako anonimowych metod i drzew ekspresji jest czysto czas kompilacji.

Func<int> myFunc = () => 10; // similar to: int myAnonMethod() { return 10; }

Efektywnie skompiluje się do metody IL, która nie otrzymuje nic i zwraca 10.

Expression<Func<int>> myExpression = () => 10;

Zostanie przekonwertowana do struktury danych opisującej wyrażenie, które nie otrzyma żadnych parametrów i zwróci wartość 10:

Expression vs Funcpowiększ obraz

Podczas gdy oba wyglądają tak samo w czasie kompilacji, to co generuje kompilator jest zupełnie inne .

 973
Author: Mehrdad Afshari,
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
2011-01-20 03:01:48

Dodaję odpowiedź dla noobów, ponieważ te odpowiedzi wydawały mi się ponad głową, dopóki nie zdałem sobie sprawy, jakie to proste. Czasami to twoje oczekiwanie, że to skomplikowane, sprawia, że nie jesteś w stanie "owinąć głowy wokół tego".

Nie musiałem rozumieć różnicy, dopóki nie natrafiłem na naprawdę irytujący "błąd", próbując użyć LINQ-to-SQL generycznie:

public IEnumerable<T> Get(Func<T, bool> conditionLambda){
  using(var db = new DbContext()){
    return db.Set<T>.Where(conditionLambda);
  }
}

To działało świetnie, dopóki nie zacząłem się OutofMemoryExceptions na większych zestawach danych. Ustawianie punktów przerwania wewnątrz lambda uświadomiła mi, że to iteracja przez każdy wiersz w moim stole jeden po drugim, szukając dopasowań do mojego stanu lambda. To mnie na chwilę zaskoczyło, bo dlaczego do cholery traktuje moją tabelę danych jako gigantyczną liczbę liczbową zamiast robić LINQ-to-SQL, jak powinno? To samo działo się również w moim odpowiedniku LINQ-to-MongoDb.

Poprawka polegała po prostu na zamianie Func<T, bool> na Expression<Func<T, bool>>, więc wygooglowałem dlaczego potrzebuje Expression zamiast Func, kończąc proszę.

Wyrażenie po prostu zamienia delegata w dane o sobie. więc a => a + 1 staje się czymś w stylu "po lewej stronie jest int a. Po prawej stronie dodajesz do niego 1."to jest to.Możesz już iść do domu. Jest to oczywiście bardziej ustrukturyzowane, ale to w zasadzie wszystko, czym naprawdę jest drzewo ekspresji-nie ma co owijać w bawełnę.

Zrozumienie tego staje się jasne, dlaczego LINQ-to-SQL potrzebuje Expression, A Func nie jest odpowiednie. Func nie niesie ze sobą sposobu, aby dostać się do siebie, aby zobaczyć nitty-gritty jak przetłumaczyć go na zapytanie SQL/MongoDb/inne. Nie widać, czy to dodawanie czy mnożenie po odejmowaniu. Wszystko, co możesz zrobić, to go uruchomić. Expression, z drugiej strony, pozwala zajrzeć do wnętrza delegata i zobaczyć wszystko, co chce zrobić, upoważniając Cię do przetłumaczenia go na cokolwiek chcesz, na przykład zapytanie SQL. Func nie zadziałało, ponieważ mój DbContext był ślepy na to, co faktycznie było w lambdzie wyrażenie, aby przekształcić go w SQL, więc zrobił następną najlepszą rzecz i powtórzył, że warunkowy przez każdy wiersz w mojej tabeli.

Edit: wypowiedzenie mojego ostatniego zdania na prośbę Jana Piotra:

IQueryable rozszerza IEnumerable, więc metody IEnumerable, takie jak Where() uzyskują przeciążenia, które akceptują Expression. Kiedy zdasz Expression do tego, zachowujesz IQueryable w wyniku, ale kiedy zdasz Func, spadasz z powrotem na bazę IEnumerable i otrzymasz IEnumerable w wyniku. Innymi słowy, nie zauważając, że zmieniłeś swój zestaw danych w listę do iteracji, a nie coś do odpytywania. Trudno zauważyć różnicę, dopóki nie spojrzysz pod maskę na podpisy.

 220
Author: Chad Hedgcock,
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
2016-10-07 16:34:01

Niezwykle ważną kwestią przy wyborze wyrażenia vs Func jest to, że IQueryable providers jak LINQ to Entities mogą "trawić" to, co przekazujesz w wyrażeniu, ale zignorują to, co przekazujesz w Func. Mam dwa posty na blogu na ten temat:

Więcej o Expression vs Func with Entity Framework and Falling in Love with LINQ-Part 7: Expressions and Funcs (The last section)

 92
Author: LSpencer777,
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
2012-01-11 15:57:43

Chciałbym dodać kilka uwag na temat różnic między Func<T> i Expression<Func<T>>:

  • Func<T> jest tylko normalnym Old-school MulticastDelegate;
  • Expression<Func<T>> jest reprezentacją wyrażenia lambda w postaci drzewa wyrażeń;
  • drzewo wyrażeń może być skonstruowane poprzez składnię wyrażeń lambda lub poprzez składnię API;
  • drzewo wyrażeń może być skompilowane do delegata Func<T>;
  • konwersja odwrotna jest teoretycznie możliwa, ale jest to rodzaj dekompilacja, nie ma do tego wbudowanej funkcjonalności, ponieważ nie jest to prosty proces;
  • drzewo wyrażeń można obserwować/tłumaczyć/modyfikować za pomocą ExpressionVisitor;
  • metody rozszerzenia dla IEnumerable działają z Func<T>;
  • metody rozszerzenia dla IQueryable działają z Expression<Func<T>>.

Jest artykuł, który opisuje szczegóły za pomocą próbek kodu:
LINQ: Func vs. Expression > .

Mam nadzieję, że będzie pomocne.

 60
Author: Olexander,
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
2013-06-11 08:34:04

Więcej filozoficznego wyjaśnienia na ten temat można znaleźć w książce Krzysztofa Cwaliny(Framework Design Guidelines: Conventions, Idioms, and Patterns for Reusable. NET Libraries);

Rico Mariani

Edit for non-image version:

najczęściej będziesz chciał Func lub Action jeśli wszystko, co musi się stać, to uruchomić jakiś kod. Potrzebujesz wyrażenia, gdy kod wymaga analizy, serializowane lub zoptymalizowane przed uruchomieniem. wyrażenie służy do myślenia o kodzie, Func / Action służy do jego uruchomienia.

 46
Author: Oğuzhan Soykan,
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
2017-10-11 18:00:54

LINQ jest kanonicznym przykładem (na przykład rozmowa z bazą danych), ale tak naprawdę, za każdym razem, gdy bardziej zależy ci na wyrażaniu tego, co zrobić, a nie faktycznie to robić. Na przykład, używam tego podejścia w stosie RPC protobuf-net (aby uniknąć generowania kodu itp.) - więc wywołujesz metodę z:

string result = client.Invoke(svc => svc.SomeMethod(arg1, arg2, ...));

To dekonstruuje drzewo wyrażeń do rozwiązania SomeMethod (i wartość każdego argumentu), wykonuje wywołanie RPC, aktualizuje dowolne ref/out args i zwraca wynik połączenia zdalnego. Jest to możliwe tylko za pomocą drzewa wyrażeń. I cover this more here .

Innym przykładem jest ręczne budowanie drzew wyrażeń w celu kompilacji do lambda, jak to robi się za pomocą kodu operatorów ogólnych.

 32
Author: Marc Gravell,
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-08-13 05:57:02

Możesz użyć wyrażenia, gdy chcesz traktować swoją funkcję jako dane, a nie jako kod. Możesz to zrobić, jeśli chcesz manipulować kodem (jako danymi). Przez większość czasu, jeśli nie widzisz potrzeby wyrażeń, prawdopodobnie nie musisz ich używać.

 17
Author: Andrew Hare,
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-04-27 13:53:05

Głównym powodem jest to, że nie chcesz uruchamiać kodu bezpośrednio, ale chcesz go sprawdzić. Może to być z wielu powodów:

  • mapowanie kodu do innego środowiska (tj. C # code to SQL in Entity Framework)
  • zastępowanie części kodu w trybie runtime (programowanie dynamiczne lub nawet zwykłe suche techniki)
  • Walidacja kodu (bardzo przydatna przy emulowaniu skryptów lub podczas analizy)
  • serializacja-wyrażenia mogą być serializowane raczej łatwo i bezpiecznie, delegaci nie mogą
  • silnie wpisane bezpieczeństwo na rzeczy, które nie są z natury mocno wpisane, i wykorzystanie sprawdzeń kompilatora, mimo że wykonujesz dynamiczne wywołania w trybie runtime (ASP.NET MVC 5 z maszynką jest dobrym przykładem)
 15
Author: Luaan,
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
2014-03-26 12:54:33

Nie widzę jeszcze żadnych odpowiedzi, które wspominają o wydajności. Przekazywanie Func<> s do Where() lub Count() jest złe. Naprawdę źle. Jeśli użyjesz Func<> to wywoła IEnumerable LINQ zamiast IQueryable, co oznacza, że całe tabele są ściągane inastępnie filtrowane. Expression<Func<>> jest znacznie szybszy, zwłaszcza jeśli odpytywasz bazę danych, która mieszka na innym serwerze.

 5
Author: mhenry1384,
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
2017-06-16 15:58:20