2013-02-15 12 views
6

Nur aus Neugier, warum behandelt der Compiler eine unbeschränkte generische Art anders als es typeof (Objekt) würde?Die Regeln von Generika und Typ Einschränkungen

In den oben genannten, Casting "T Ding" zu Bar führt zu einem Compiler-Fehler. Das "Objekt-Ding" zu Bar zu bringen, ist etwas, was der Compiler mir erlaubt, natürlich auf mein eigenes Risiko.

Was ich nicht sehe, ist warum. In .net-Objekt nach allem ist ein Catch-All und der Laufzeittyp könnte ein Boxed-Wert oder ein Objekt eines beliebigen Typs sein. Ich sehe also nicht, welchen logischen Grund es für den Compiler gibt, zwischen den beiden Fällen zu unterscheiden. Das Beste, was ich tun kann, ist so etwas wie "Der Programmierer würde erwarten, dass der Compiler Typtests mit generischen Typen durchführt, aber nicht mit Objekten". :) Ist das alles da?

Btw, ich bin mir bewusst, dass ich immer noch meine Besetzung im Foo Fall durch einfaches Schreiben erledigen

((Bar)(object)thing).ToString(); 

Ich möchte nur verstehen, warum die Compiler dies tun ...

+3

Ist die Kompilierzeit legal, um 'int' in' Bar' zu übertragen? Wenn Sie diesen Typparameter mit 'int' ausfüllen, sollte * dann * Compilerfehler auftreten? Was ist, wenn die Baugruppe nicht von Ihnen stammt, sodass Sie das Problem nicht sehen können? T ist kein Objekt. Es ist etwas unglaublich Spezifisches. –

+1

Ist dir auch bekannt, dass du sagen kannst "Klasse Foo wo T: Bar" zu _ensure_ dass "T" immer in "Bar" umgewandelt werden kann? – Rawling

+1

Ich bin sicher, dass Eric Lippert irgendwo einen Blogeintrag darüber hat, aber ich finde es nicht ... –

Antwort

4

Die Bedeutung hier ist object. Wenn das erste Beispiel etwas anderes als object wäre, würde es sich gleich verhalten. Im Grunde, was Sie im Moment sagen hier:

(Bar)thing 

ist: "ein T zu einem Bar konvertieren"; was im allgemeinen Fall nicht annähernd legal ist. Durch object Hinzufügen Sie es machen:

(Bar)(object)thing 

die „convert eine T zu einem object ...“ ist - was immer legal ist, da object die Wurzel aller verwalteten Typen ist; und beachten Sie, dass dies eine Box einfügen kann - "... und dann die object als Bar" - wiederum; Es ist immer zur Kompilierzeit legal und beinhaltet eine Typüberprüfung ("unbox-any") zur Laufzeit.

Zum Beispiel: Angenommen, TDateTime ist ...

DateTime thing = ... 
Bar bar = (Bar)(object)thing; 

perfekt gültig ist; Sicher, es wird zur Laufzeit scheitern, aber: Dies ist das Szenario, das Sie im Auge behalten müssen.

+0

Während ich richtig war, fragte der OP nach der Designentscheidung hinter der Entscheidung, den Fehler bei * compile * time für den letzteren Fall, aber bei * run * time für den ersteren anzugeben (vorausgesetzt, die Sache ist nicht in 'Bar' umwandelbar. –

+0

Sie sind beide (@Marc, @Ron) genau richtig.Ich denke, das ist eine gute Erklärung.Ein Objekt in eine Sache zu werfen ist immer ein Niedergang, etwas Spezifischeres, aber der allgemeine Fall kann beides sein, und daher das * Konstruierte * Typ würde nicht für irgendein T kompilieren - was ein guter Grund ist, einen Kompilierungsfehler zu geben! –

4

Es kommt auf die Semantik und den Zweck der Erstellung von Generika. Wenn Sie einen allgemeinen Typ T haben, lässt der Compiler Sie nicht beliebig direkt auf ein anderes Objekt umwandeln. Dies ist sinnvoll, da der Zweck von T darin besteht, den Programmierer zu zwingen, zu spezifizieren, welcher Typ T tatsächlich ist. Es wird nicht "Objekt" sein, sondern ein bestimmter Objekttyp. Zur Kompilierzeit kann der Compiler nicht wissen, was in T sein wird und kann daher nicht umwandeln.

Das Umwandeln von Objekt funktioniert, da es ein anonymes Objekt ist - im Gegensatz zu einem KNOWN-Objekttyp, der in seiner Verwendung definiert wird.

Dies kann mit der Where-Klausel erweitert werden. Z.B., können Sie angeben, dass T vom Typ IBar sein muss;

interface IBar { } 

class Bar : IBar { } 

class Foo<T> 
    where T : IBar 
{ 
    void foo(T thing) 
    { 
     ((IBar)thing).ToString(); 
    } 
} 

Vererbung funktioniert auch mit der where-Klausel;

class Bar { } 

class Foo<T> 
    where T : Bar 
{ 
    void foo(T thing) 
    { 
     // now you don't need to cast at all as the compiler knows 
     // exactly what type T is (at a parent level at least) 
     thing.ToString(); 
    } 
} 
+0

Ich bin glücklich, dies als Antwort zu markieren, obwohl ich finde, dass deine Argumentation wenig mehr als mein ursprünglicher Vorschlag ist, es kocht wirklich bis zu den Erwartungen des "Benutzers" (Benutzer meiner generischen Klasse, das heißt, oder eher eine konstruierte Klasse, wo T definiert wurde). Zu sagen, dass "T nicht nur ein Objekt ist, es ist etwas Spezifisches" ist, IMHO, Überflüssig Jedes Objekt kann und ist normalerweise etwas sehr sp aussagekräftig. Und es gibt verschiedene Möglichkeiten, dasselbe Objekt anzuzeigen. Ich möchte vielleicht jeden Typ unterstützen, mich aber auf eine bestimmte Art verhalten, wenn der Typ mir bekannt ist, zum Beispiel - generische Klasse oder nicht. –

+0

@TheDag Das Ändern des Verhaltens eines Generikers basierend auf dem Typ T ist im Allgemeinen keine gute Idee, obwohl es gültige Fälle für die Behandlung bestimmter Typen gibt, z. B. Boxed- oder Unboxed-Typen. In Ihrem Beispiel versuchen Sie, direkt von T zu gehen, was standardmäßig ein bestimmter Typ für einen anderen spezifischen Typ "Bar" sein wird. Dies wäre ein Versuch, '(Foo) Bar 'zu tun, wenn es keine Beziehung oder gemeinsame Schnittstelle gibt. Ich denke immer noch, dass die Compiler-Warnung eine gültige ist – DiskJunky

Verwandte Themen