2009-10-20 20 views
21

Mam metodę z następującym podpisem:napisać metodę, która akceptuje wyrażenia lambda

void MyMethod(Delegate d){}; 
void MyMethod(Expression exp){}; 
void MyMethod(object obj){}; 

to jednak nie trafia do kompilacji:

MyMethod((int a) => a) 

z powodu następującego błędu:

"Cannot convert lambda expression to type 'object' because it is not a delegate type" 

Dlaczego to nie działa?

Edycja: wiem, że to działa. Kompilator kompiluje wyrażenie lambda do delgate w tym przypadku myślę.

void MyMethod(Func<int, int> d){}; 

poważaniem,

+3

Fyi, Ilekroć coś nie uda się skompilować, przeczytaj (i opublikuj) komunikat o błędzie. –

+0

@SharePoint Newbie: Zobacz mój zaktualizowany post. To powinno rozwiązać twój błąd. – Noldorin

Odpowiedz

17

Ponieważ typ System.Delegate nie jest "Delegatem". To tylko klasa bazowa. Musisz użyć typu delegata z poprawnym podpisem. Zdefiniować metodę następująco:

void MyMethod(Func<int, int> objFunc) 

EDIT:

myMethod (obiektu) nie działa, ponieważ wyrażenie lambda ma typ na swój własny, ale typ jest wywnioskować z typem lokalizacji to jest przypisany do. Obiekt nie działa. Musisz użyć typu delegata z prawidłowym podpisem.

+0

Wiem, że to działa, chcę wiedzieć, dlaczego inne sygnatury nie działają. –

+0

Potem przeczytaj, co napisałem. System.Delegate NIE jest delegatem, wyrażenie lambda nie może zostać przekonwertowane na System.Delegate. W rzeczywistości wyrażenia lambda nie mają własnego typu, pobierają swój typ z typu zmiennej, do której są przypisane, więc obiekt również nie działa. –

+0

Ale sam określam typ. Nie jest to implicite wywnioskowane, "(int a) => a;". Czy mógłbyś wyjaśnić, że wyrażeń lambda nie ma sam w sobie? –

12
void MyMethod(Action<int> lambdaHereLol) 
{ 
    lambdaHereLol(2); 
} 

w użyciu:

var hurrDurr = 5; 
MyMethod(x => Console.Write(x * hurrDurr)); 

C# jest językiem statycznie wpisane. Kompilator musi znać typ wszystkiego, z czym ma do czynienia. Lambdas są nieco trudne do przybicia, a czasami kompilator nie może tego rozgryźć. W powyższym przykładzie, jeśli MyMethod wziął obiekt, kompilator nie mógł dowiedzieć się, że x jest int (mój przykład jest prosty, ale nic nie mówi, że nie może być dużo bardziej skomplikowany i trudniejszy do ustalenia). Muszę więc wyraźniej określać metodę, która przyjmuje moją lambdę.

+1

Ktoś jeszcze miesza "cloture" i "closure"? – Will

+3

+1 dla selsyki kodera trolla – Filip

+1

"C# jest statycznie napisanym językiem, kompilator musi znać typ wszystkiego, z czym ma do czynienia.". To prawda, ale reszta nie następuje. Istnieje wiele statycznie napisanych języków, które umożliwiają określenie typu podpisu. F # to jeden przykład. – rgrinberg

2

Spróbuj tego:

void MyMethod(Action<int> func) { } 

Trzeba silnie wpisany delegata jako parametr do metody. Powodem niepowodzenia innych wywołań jest to, że kompilator języka C# nie zezwoli na przekazanie wyrażenia lambda do metody oczekującej wartości Object, ponieważ wyrażenie lambda nie zawsze musi być delegatem we wszystkich przypadkach. Ta sama zasada dotyczy przekazywania wyrażenia lambda jako Delegate.

Po przejściu lambda do funkcji takiej jak pokazałem powyżej, kompilacja może bezpiecznie założyć, że chcesz, aby wyrażenie lambda zostało przekonwertowane na określony typ delegata i robi to.

+0

Wiem, że to działa, chcę wiedzieć, dlaczego inne sygnatury nie działają. –

0

To po prostu charakter kompilatora, który należy jawnie rzucić obiekt delegata na Delegate, przekazując go jako parametr typu Delegate. W rzeczywistości wyrażenia lambda komplikują rzeczy jeszcze bardziej, ponieważ nie są one w domyśle przekształcane w delegatów w tym przypadku.

Czego wymagać to podwójna obsada, jako takich:

MyMethod((Delegate)(Func<int, int>)((int a) => a)); 

co oczywiście odnosi się do podpisania metoda:

void MyMethod(Delegate d); 

w zależności od sytuacji, może chcesz zdefiniować parametr typu Func<int> zamiast Delegate (choć wahałbym się dodając przeciążenie, tylko dlatego, że dodaje niepotrzebnej złożoności w uczciwości).

+0

To też nie działa. –

+0

Tak, ponieważ, jak już napisałem, System.Delegate NIE jest delegatem. To tylko typ podstawowy dla delegatów. –

+1

Woops, masz rację. Przegapiłem konwersję na typ delegata, który musi wystąpić najpierw. Ten rodzaj daje wgląd w to, w jaki sposób kompilator C# dokonuje niejawnej konwersji wyrażeń lambda na delegatów i jak przekazuje to jako parametr. To, co tu widzisz, robi wszystko "ręcznie". – Noldorin

3

Element lambda podobny do (int a) => a będzie pasował do każdego delegata, który pobiera int i zwraca wartość int. Func<int,int> to tylko jeden przykład i można go łatwo zadeklarować za pomocą delegate int Foo(int x);. W rzeczywistości to wyrażenie lambda będzie nawet pasować do delegata, który pobiera int i zwraca double, ponieważ wynik lambda (a) jest domyślnie wymienialny na double.

Aby lambda mogła być przypisana do wszystkich typów uczestników, do których byłaby dopasowana, sama lambda z natury nie posiada typu. Zamiast tego przyjmuje typ delegata, którego używasz, o ile jest to możliwe. ((int a) => a nie może być przypisana do Func<byte, byte> oczywiście).

Chociaż zarówno Func<int, int> i Foo pełnomocnika I określonych oczywiście może być przekształcony Delegate, lambda nie może być przetwarzany bezpośrednio do Delegate, ponieważ nie wiadomo, co jej rzeczywisty podpis byłby wtedy. Po Delegate d = (int a) => a, będzie d być Foo lub Func<int, int>, a nawet Func<int, double>? Wszystkie są poprawnymi możliwościami, a kompilator nie ma pojęcia, co zamierzałeś. Mogłoby to najlepiej zgadywać, ale C# nie jest językiem, który robi takie zgadywanie. Właśnie dlatego nie możesz zrobić czegoś takiego jak var = (int a) => a.

uważam, że komunikat o błędzie, że kompilator daje za Delegate d = (int a) => a; jest bardzo jasne:

Cannot convert lambda expression to type 'System.Delegate' because it is not a delegate type

Intuicyjnie można by pomyśleć Delegate to typ delegata, ale to nie jak to wszystko działa. :)

0

Powodem tego niepowodzenia jest ten sam powód, dla którego wyrażenie takie jak "object del = (int a) => a" lub nawet "var del = (int a) => a" zawiedzie. Można by pomyśleć, że kompilator może obliczyć typ twojego wyrażenia lambda, ponieważ jawnie podajesz typ argumentu, ale nawet wiedząc, że wyrażenie przyjmuje int i zwraca int, istnieje pewna liczba typów delegatów, które można przekonwertować do. Typ delegatów Func jest najczęściej używany do generycznych funkcji, takich jak ta, ale jest to tylko konwencja i nic o tym nie wie kompilator.

Co musisz zrobić, to rzucić wyrażenie lambda na konkretny typ delegata, aby kompilator wybrał przeciążenie Delegata, używając normalnej składni rzutowania (Func) ((int a) => a), lub używając składni konstruktora delegata new Func ((int a) => a).

Poza tym zwykle nie chcesz używać klasy bez klauzuli Delegate, chyba że musisz wywołać coś inaczej, w zależności od liczby akceptowanych argumentów. Prawie zawsze lepiej jest zaakceptować Func lub Action dla rzeczy takich jak callbacks.

Powiązane problemy