Delegate.CreateDelegate wird keinen Rückgabewert enthalten - absichtlich, oder eine Unterlassung?

8

Ich habe eine statische Methode:

%Vor%

Und ich muss in der Lage sein, es mit Hilfe eines Type -Parameters aufzurufen, auf den es viele geben könnte. Daher ist mein Standardmuster, einen Thread-sicheren Cache von Delegaten zu erstellen (mit ConcurrentDictionary in .Net 4) Rufen Sie die Methode Foo<T> mit der korrekten T dynamisch auf. Ohne das Zwischenspeichern lautet der Code jedoch:

%Vor%

Dies ist nicht das erste Mal, dass ich dies tun musste - und in der Vergangenheit habe ich Expression-Bäume verwendet, um einen Proxy zu erstellen und zu kompilieren, um die Zielmethode aufzurufen - um sicherzustellen, dass die Rückgabetypkonvertierung und das Boxen von int - & gt ; Objekt (zum Beispiel) wird korrekt behandelt.

Update - Beispiel für einen funktionsfähigen Ausdruckscode

%Vor%

Was amüsant ist, ist, dass ich schon früh gelernt habe, dass ein explizites Convert erforderlich war und es akzeptierte - und anstelle der Antworten macht es jetzt Sinn, warum das .Net-Framework nicht automatisch gleichwertig bleibt in.

Aktualisierung beenden

Allerdings dachte ich diesmal, ich würde einfach Delegate.CreateDelegate verwenden, da es großartig ist, dass (von MSDN):

  

Entsprechend ist der Rückgabetyp eines Delegaten mit dem Rückgabetyp einer Methode kompatibel, wenn der Rückgabetyp der Methode restriktiver ist als der Rückgabetyp des Delegaten, da dies garantiert, dass der Rückgabewert der Methode sein kann wird sicher in den Rückgabetyp des Delegaten umgewandelt.

Jetzt - wenn ich typeof(string) an die Methode LateFoo übergebe, ist alles in Ordnung.

Wenn ich jedoch typeof(int) übergebe, erhalte ich einen ArgumentException für den Aufruf CreateDelegate , Nachricht: Error binding to target method . Es gibt keine innere Ausnahme oder weitere Informationen.

Es scheint also so zu sein, dass object für Methodenbindungszwecke nicht restriktiver als int betrachtet wird. Offensichtlich muss dies damit zu tun haben, dass das Boxen eine andere Operation als eine einfache Typkonvertierung ist und Werttypen im .NET-Framework nicht als Kovariante für object behandelt werden. trotz der tatsächlichen Typbeziehung zur Laufzeit.

Der C # -Compiler scheint damit übereinzustimmen (nur die kürzeste Art, den Fehler zu modellieren, zu ignorieren, was der Code tun würde):

%Vor%

Kompiliert nicht, weil die Foo -Methode 'den falschen Rückgabetyp hat' - wenn das CreateDelegate -Problem gegeben wird, folgt C # einfach dem .Net-Befehl.

Mir scheint, dass .Net bei der Behandlung der Kovarianz inkonsistent ist - entweder ist ein Werttyp object oder nicht; &Ampere; wenn es nicht ist, sollte es object nicht als Basis aussetzen (trotz wie viel schwieriger würde es unser Leben machen). Da es object als Basis zur Verfügung stellt (oder ist es nur die Sprache, die das tut?), Sollte ein Werttyp entsprechend der Logik kovariant sein für object (oder was auch immer Sie sagen sollen) Damit wird dieser Delegat korrekt gebunden. Wenn diese Kovarianz nur durch einen Boxvorgang erreicht werden kann; dann sollte der Rahmen dafür sorgen.

Ich wage zu sagen, dass die Antwort hier lautet, dass CreateDelegate nicht sagt, dass es eine Box-Operation in Kovarianz behandelt, weil es nur das Wort "Cast" verwendet. Ich erwarte auch, dass es ganze Abhandlungen über das breitere Thema der Werttypen und der Objektkovarianz gibt, und ich schreie über ein längst verstorbenes und etabliertes Thema. Ich denke, es gibt etwas, das ich entweder nicht verstehe oder vermisst habe - also bitte erleuchtet!

Wenn dies nicht zu beantworten ist - ich lösche es gerne.

    
Andras Zoltan 11.01.2012, 10:58
quelle

3 Antworten

7

Sie können einen Delegaten nur auf diese Weise konvertieren, wenn die Parameter und der Rückgabewert unter Verwendung einer die Konvertierung erhaltenden Darstellung konvertiert werden können.

  • Referenztypen können auf diese Weise nur in andere Referenztypen konvertiert werden
  • Integrale Werte können in andere ganzzahlige Werte derselben Größe konvertiert werden (int, uint und enums derselben Größe sind kompatibel)

Ein paar weitere relevante Blog-Artikel:

  

Diese Dichotomie motiviert noch ein weiteres Klassifikationsschema für Konvertierungen (†). Wir können Conversions in representationserhaltende Conversions (B nach D) und in darstellungsändernde Conversions (T in U) aufteilen. (‡) Wir können uns Repräsentationen erhaltende Konvertierungen von Referenztypen als solche Konvertierungen vorstellen, die die Identität des Objekts bewahren. Wenn Sie ein B in ein D umwandeln, tun Sie nichts mit dem vorhandenen Objekt. du verifizierst nur, dass es tatsächlich der Typ ist, den du sagst, und weiter geht. Die Identität des Objekts und der Bits, die die Referenz darstellen, bleiben gleich. Aber wenn Sie ein int zu einem double umwandeln, sind die resultierenden Bits sehr unterschiedlich.

     

Aus diesem Grund erfordern kovariante und kontravariante Konvertierungen von Interface- und Delegattypen, dass alle Argumente mit variablem Typ Referenztypen sind. Um sicherzustellen, dass eine Variantenreferenzkonvertierung immer identitätsbewahrend ist, müssen alle Konvertierungen mit Typargumenten auch identitätsbewahrend sein. Der einfachste Weg, um sicherzustellen, dass alle nicht-trivialen Konvertierungen bei Typargumenten identitätsbewahrend sind, besteht darin, sie als Referenzkonvertierungen zu definieren.    Ссылка

     

"aber wie kann ein Wert, wie int, der 32 Bits Speicher ist, nicht mehr und nicht weniger, möglicherweise vom Objekt erben? Ein Objekt im Speicher ausgelegt ist viel größer als 32 Bits, es hat einen Sync-Block und eine virtuelle Funktionstabelle und alle möglichen Sachen drin. " Anscheinend denken viele Leute, dass die Vererbung etwas damit zu tun hat, wie ein Wert im Speicher angelegt wird. Aber wie ein Wert im Speicher angelegt wird, ist ein Implementierungsdetail, keine vertragliche Verpflichtung der Erbschaftsbeziehung! Wenn wir sagen, dass int von Objekt erbt, meinen wir, dass, wenn das Objekt ein Mitglied hat - etwa ToString -, int auch dieses Mitglied hat.    Ссылка

    
CodesInChaos 11.01.2012, 11:38
quelle
6
  

Mir scheint, dass .NET in seiner Behandlung der Kovarianz inkonsistent ist - entweder ist ein Werttyp ein Objekt oder nicht; Wenn nicht, sollte das Objekt nicht als Basis angezeigt werden.

Es hängt davon ab, was die Bedeutung von "ist" ist, wie Präsident Clinton berühmt sagte.

Für die Zwecke der Kovarianz ist int kein Objekt , da int nicht zuordnungsmäßig mit dem Objekt kompatibel ist. Eine Variable vom Typ Objekt erwartet, dass ein bestimmtes Bitmuster mit einer bestimmten Bedeutung darin gespeichert wird. Eine Variable vom Typ int erwartet ein bestimmtes Bitmuster mit einer bestimmten Bedeutung, aber eine andere Bedeutung als die Bedeutung einer Variablen des Objekttyps.

Für die Zwecke der Vererbung ist ein int ein Objekt , weil jedes Mitglied des Objekts auch ein Mitglied von int ist. Wenn Sie eine Methode von object - ToString, say - on int, aufrufen möchten, können Sie dies sicher tun, weil ein int eine Art Objekt ist und ein Objekt über ToString verfügt.

Es ist bedauerlich, stimme ich zu, dass der Wahrheitswert von "ein int ein Objekt" ist, hängt davon ab, ob Sie "mit Zuordnung kompatibel zu" oder "ist eine Art von" bedeuten.

  

Wenn diese Kovarianz nur durch einen Boxvorgang erreicht werden kann; dann sollte der Rahmen dafür sorgen.

OK. Woher? Wohin soll der Box-Betrieb gehen? Irgendwo muss jemand ein Stück IL erzeugen, das eine Box-Anweisung hat. Schlägst du vor, wenn das Framework sieht:

%Vor%

Dann sollte das Framework automatisch so tun, als hättest du gesagt:

%Vor%

und damit den Box-Befehl generieren?

Das ist eine vernünftige Eigenschaft, aber was sind die Konsequenzen? Func<int> und Func<object> sind Referenztypen. Wenn Sie f2 = f1 für Referenztypen wie diesen verwenden, erwarten Sie nicht, dass f2 und f1 Referenzidentität haben? Wäre es nicht sonderlich komisch, dass dieser Testfall scheitert?

%Vor%

Wenn das Framework dieses Feature implementiert, würde es das tun.

Ebenso, wenn Sie sagten:

%Vor%

und Sie haben die beiden Delegierten gefragt, ob sie sich auf dieselbe Methode beziehen oder nicht, wäre es nicht sonderlich komisch, wenn sie sich auf verschiedene Methoden beziehen würden?

Ich denke, das wäre komisch. Allerdings die VB-Designer nicht . Wenn Sie versuchen, solche Spielereien in VB zu ziehen, wird der Compiler Sie nicht aufhalten. Der VB-Code-Generator erzeugt für Sie nicht-referenzgleiche Delegaten, die sich auf verschiedene Methoden beziehen. Probieren Sie es aus!

Moral der Geschichte: vielleicht ist C # nicht die Sprache für dich. Vielleicht bevorzugen Sie eine Sprache wie VB, in der die Sprache so konzipiert ist, dass sie eine "Vermutung über das, was der Benutzer wahrscheinlich gemeint hat und es einfach zum Laufen bringt" entwickelt. Das ist nicht die Einstellung der C # -Designer. Wir sind mehr "sagen Sie dem Benutzer, wenn etwas verdächtig falsch aussieht und lassen Sie sie herausfinden, wie sie es beheben wollen" Art von Menschen.

    
Eric Lippert 11.01.2012 21:38
quelle
4

Obwohl ich denke, dass @CodeInChaos absolut richtig ist, kann ich nicht helfen, dieser Blog von Eric Lippert Post out. Als Antwort auf den letzten Kommentar zu seinem Beitrag (ganz unten auf der Seite) erklärt Eric die Begründung für ein solches Verhalten, und ich denke, genau daran interessiert Sie.

    
Igor Korkhov 11.01.2012 13:13
quelle

Tags und Links