Warum ist die Kompilierung in Ordnung, wenn ich die Invoke-Methode verwende, und nicht in Ordnung, wenn ich Func <int, int> direkt zurückgebe?

28

Ich verstehe diesen Fall nicht:

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
}

Warum ist die Kompilierung in Ordnung, wenn ich die InvokeMethode verwende, und nicht in Ordnung, wenn ich csharp Func<int,int>direkt zurückkehre?

Evgeniy Terekhin
quelle
Sie haben einen Delegierten, was bedeutet, dass Sie eine Art Ereignis erhalten. Der Aufruf verhindert eine threadübergreifende Ausnahme und ermöglicht mehreren Prozessen den Zugriff auf das Objekt.
Jdweng
Beachten Sie, dass dieses Problem auch dann angezeigt wird, wenn Sie zwei identisch aussehende Delegierte wie delegate void test1(int i);und verwendendelegate void test2(int i);
Matthew Watson

Antworten:

27

Es gibt zwei Dinge, die Sie wissen müssen, um dieses Verhalten zu verstehen.

  1. Alle Delegierten stammen von ab System.Delegate, aber unterschiedliche Delegierte haben unterschiedliche Typen und können daher nicht einander zugewiesen werden.
  2. Die C # -Sprache bietet eine spezielle Behandlung für die Zuweisung einer Methode oder eines Lambda zu einem Delegaten .

Da verschiedene Delegaten unterschiedliche Typen haben, können Sie einen Delegaten eines Typs keinem anderen zuweisen.

Zum Beispiel gegeben:

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

Dann:

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

In der ersten Zeile oben wird OK kompiliert, da die spezielle Behandlung zum Zuweisen eines Lambda oder einer Methode zu einem Delegaten verwendet wird.

Tatsächlich wird diese Zeile vom Compiler effektiv wie folgt umgeschrieben:

test1 a = new test1(Console.WriteLine);

Die zweite Zeile oben wird nicht kompiliert, da versucht wird, eine Instanz eines Typs einem anderen inkompatiblen Typ zuzuweisen.

Was die Typen betrifft, gibt es keine kompatible Zuordnung zwischen test1und, test2da es sich um verschiedene Typen handelt.

Wenn es hilfreich ist, darüber nachzudenken, berücksichtigen Sie diese Klassenhierarchie:

class Base
{
}

class Test1 : Base
{
}

class Test2 : Base
{
}

Der folgende Code wird nicht kompiliert, obwohl Test1und Test2stammt aus der gleichen Basisklasse:

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

Dies erklärt, warum Sie einen Delegatentyp keinem anderen zuweisen können. Das ist nur die normale C # -Sprache.

Entscheidend ist jedoch, zu verstehen, warum Sie einem kompatiblen Delegaten eine Methode oder ein Lambda zuweisen dürfen. Wie oben erwähnt, ist dies Teil der C # -Sprachenunterstützung für Delegierte.

Also endlich um deine Frage zu beantworten:

Wenn Sie verwenden Invoke(), weisen Sie dem Delegaten einen METHOD-Aufruf zu, indem Sie die spezielle C # -Sprachenbehandlung verwenden, um einem Delegaten Methoden oder Lambdas zuzuweisen, anstatt zu versuchen, einen inkompatiblen Typ zuzuweisen. Daher wird OK kompiliert.

Um ganz klar zu sein, der Code, der in Ihrem OP kompiliert wird:

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

Wird tatsächlich konzeptionell in etwas umgewandelt wie:

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

Während der fehlerhafte Code versucht, zwischen zwei inkompatiblen Typen zuzuweisen:

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

Im zweiten Fall fist vom Typ Func<int, int>, aber die Methode soll a zurückgeben test. Dies sind nicht verwandte (delegierte) Typen, die nicht miteinander konvertierbar sind, sodass ein Compilerfehler auftritt. Sie können zu diesem Abschnitt der Sprachspezifikation gehen und nach "Delegat" suchen. Conversions zwischen Delegaten mit denselben Signaturen werden nicht erwähnt.

Im ersten Fall handelt es sich jedoch f.Invokeum einen Methodengruppenausdruck , der eigentlich keinen Typ hat. Der C # -Compiler konvertiert Methodengruppenausdrücke je nach Kontext durch eine Methodengruppenkonvertierung in bestimmte Delegatentypen .

(Zitiert die 5. Kugel hier , Hervorhebung von mir)

Ein Ausdruck wird als einer der folgenden klassifiziert:

  • ...

  • Eine Methodengruppe, bei der es sich um eine Reihe überladener Methoden handelt, die sich aus einer Mitgliedersuche ergeben. [...] Eine Methodengruppe ist in einem invocation_expression, einem delegate_creation_expression und als linke Seite eines isOperators zulässig und kann implizit in einen kompatiblen Delegatentyp konvertiert werden.

In diesem Fall wird es in den testDelegattyp konvertiert .

Mit anderen Worten, return ffunktioniert nicht, weil es fbereits einen Typ gibt, aber f.Invokenoch keinen Typ.

Kehrmaschine
quelle
2

Hier geht es um die Typkompatibilität:

Es folgt die Definition des Func- Delegaten aus MSDN-Quellen:

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

Wenn Sie sehen, dass es keine direkte Beziehung zwischen der oben genannten Funktion und Ihrem definierten Delegierten gibt:

public delegate int test(int i);

Warum das erste Snippet kompiliert wird:

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

Delegaten werden mit der Signatur verglichen, bei der es sich um Eingabeparameter und Ausgabeergebnisse handelt. Letztendlich ist ein Delegat ein Funktionszeiger, und zwei Funktionen können nur über die Signatur verglichen werden. Zur Laufzeit wird die über Func aufgerufene Methode dem TestDelegaten zugewiesen , da die Signatur identisch ist und nahtlos funktioniert. Es handelt sich um eine Funktionszeigerzuweisung, bei der der TestDelegat nun die vom Func-Delegaten angegebene Methode aufruft

Warum 2nd Snippet nicht kompiliert werden kann

Zwischen Func und dem Testdelegierten besteht keine Typ- / Zuweisungskompatibilität. Func kann nicht als Teil der Typsystemregeln ausgefüllt werden. Auch wenn das Ergebnis test delegatewie im ersten Fall zugewiesen und ausgefüllt werden kann.

Mrinal Kamboj
quelle