Dlaczego kompilacja jest OK, gdy używam metody Invoke, a nie OK, gdy zwracam bezpośrednio Func <int, int>?

28

Nie rozumiem tej sprawy:

public delegate int test(int i);

public test Success()
{
    Func<int, int> f = x => x;
    return f.Invoke; // <- code successfully compiled 
}

public test Fail()
{
    Func<int, int> f = x => x;
    return f; // <- code doesn't compile
}

Dlaczego kompilacja jest OK, gdy używam Invokemetody, a nie OK, gdy wracam csharp Func<int,int>bezpośrednio?

Evgeniy Terekhin
źródło
Masz delegata, co oznacza, że ​​dostajesz jakieś wydarzenie. Wywołanie zapobiega wyjątkowi między wątkami i umożliwia dostęp do obiektu wielu procesom.
jdweng
Zauważ, że zobaczysz ten problem, nawet jeśli użyjesz dwóch identycznie wyglądających delegatów, takich jak delegate void test1(int i);idelegate void test2(int i);
Matthew Watson

Odpowiedzi:

27

Są dwie rzeczy, które musisz wiedzieć, aby zrozumieć to zachowanie.

  1. Wszyscy delegaci pochodzą System.Delegate, ale różni delegaci mają różne typy i dlatego nie można ich przypisywać.
  2. Język C # zapewnia specjalną obsługę przypisywania metody lub lambda do delegata .

Ponieważ różni delegaci mają różne typy, oznacza to, że nie można przypisać delegata jednego typu do drugiego.

Na przykład biorąc pod uwagę:

delegate void test1(int i);
delegate void test2(int i);

Następnie:

test1 a = Console.WriteLine; // Using special delegate initialisation handling.
test2 b = a;                 // Using normal assignment, therefore does not compile.

Pierwszy wiersz powyżej kompiluje OK, ponieważ używa specjalnej obsługi do przypisywania lambda lub metody do delegata.

W rzeczywistości kompilator skutecznie przepisał następującą linię:

test1 a = new test1(Console.WriteLine);

Drugi wiersz powyżej nie kompiluje się, ponieważ próbuje przypisać wystąpienie jednego typu do innego niezgodnego typu.

Jeśli chodzi o typy, nie ma kompatybilnego przypisania pomiędzy test1i test2ponieważ są to różne typy.

Jeśli warto o tym pomyśleć, rozważ następującą hierarchię klas:

class Base
{
}

class Test1 : Base
{
}

class Test2 : Base
{
}

Poniższy kod nie będzie kompilować, choć Test1i Test2wywodzą się z tej samej klasy bazowej:

Test1 test1 = new Test1();
Test2 test2 = test1; // Compile error.

To wyjaśnia, dlaczego nie można przypisać jednego typu delegata do innego. To tylko normalny język C #.

Najważniejsze jest jednak zrozumienie, dlaczego możesz przypisać metodę lub lambda kompatybilnemu delegatowi. Jak wspomniano powyżej, jest to część obsługi języka C # dla delegatów.

Więc w końcu odpowiedz na twoje pytanie:

Gdy używasz Invoke(), przypisujesz wywołanie METHOD do delegata, korzystając ze specjalnej obsługi języka C # do przypisywania metod lub lambd do delegata zamiast próbować przypisać niezgodny typ - stąd kompilacja jest OK.

Aby być całkowicie jasnym, kod, który kompiluje się w twoim OP:

public test Success()
{
    Func<int, int> f = x => x;
    return f.Invoke; // <- code successfully compiled 
}

Jest konwertowany koncepcyjnie na coś takiego:

public test Success()
{
    Func<int, int> f = x => x;
    return new test(f.Invoke);
}

Podczas gdy uszkodzony kod próbuje przypisać dwa niezgodne typy:

public test Fail()
{
    Func<int, int> f = x => x;
    return f; // Attempting to assign one delegate type to another: Fails
}
Matthew Watson
źródło
6

W drugim przypadku fjest typu Func<int, int>, ale mówi się, że metoda zwraca a test. Są to typy niepowiązane (delegowane), które są wzajemnie niewymienne, więc występuje błąd kompilatora. Możesz przejść do tej sekcji specyfikacji języka i wyszukać „delegować”. Nie znajdziesz żadnej wzmianki o konwersjach między delegatami, którzy mają te same podpisy.

W pierwszym przypadku f.Invokejest to wyrażenie grupy metod , które w rzeczywistości nie ma typu. Kompilator C # konwertuje wyrażenia grupy metod na określone typy delegatów zgodnie z kontekstem, poprzez konwersję grupy metod .

(Cytując tutaj piąty pocisk , podkreśl moje)

Wyrażenie jest klasyfikowane jako jedno z następujących:

  • ...

  • Grupa metod, która jest zestawem przeciążonych metod wynikających z wyszukiwania elementu. [...] Grupa metod jest dozwolona w wyrażeniu invocation_expression, delegate_creation_expression oraz jako lewa strona isoperatora i może być niejawnie przekonwertowana na kompatybilny typ delegata.

W takim przypadku jest konwertowany na testtyp delegata.

Innymi słowy, return fnie działa, ponieważ fma już typ, ale f.Invokenie ma jeszcze typu.

Zamiatarka
źródło
2

Problem dotyczy zgodności typu:

Poniżej znajduje się definicja delegata Func z MSDN Sources:

public delegate TResult Func<in T, out TResult>(T arg);

Jeśli zauważysz, że nie ma bezpośredniego związku między Func wspomnianym powyżej a twoim zdefiniowanym Delegatem:

public delegate int test(int i);

Dlaczego kompilacja pierwszego fragmentu:

public test Success()
{
    Func<int, int> f = x => x;
    return f.Invoke; // <- code successfully compiled 
 }

Delegatów porównuje się za pomocą podpisu, który jest parametrami wejściowymi i wynikiem wyjściowym, ostatecznie Delegat jest wskaźnikiem funkcji, a dwie funkcje można porównać tylko poprzez podpis. W czasie wykonywania metoda wywoływana przez Func jest przypisywana Testdelegatowi, ponieważ podpis jest taki sam i działa bezproblemowo. Jest to przypisanie wskaźnika funkcji, w którym Testdelegat będzie teraz wywoływał metodę wskazaną przez delegata Func

Dlaczego 2nd Snippet się nie kompiluje

Pomiędzy Func a testowanym delegatem nie ma zgodności typu / przypisania, Func nie może wypełnić w ramach reguł systemowych Type. Nawet jeśli jego wynik można przypisać i wypełnić test delegatetak, jak w pierwszym przypadku.

Mrinal Kamboj
źródło