Ist es sinnvoll, "as" anstelle einer Besetzung zu verwenden, auch wenn es keine Nullprüfung gibt? [geschlossen]

351

In Entwicklungsblogs, Online-Codebeispielen und (kürzlich) sogar in einem Buch stolpere ich immer wieder über Code wie diesen:

var y = x as T;
y.SomeMethod();

oder noch schlimmer:

(x as T).SomeMethod();

Das ergibt für mich keinen Sinn. Wenn Sie sicher sind, dass xes sich um einen Typ handelt T, sollten Sie eine direkte Besetzung verwenden : (T)x. Wenn Sie sich nicht sicher sind, können Sie verwenden as, müssen dies jedoch überprüfen, nullbevor Sie eine Operation ausführen. Alles, was der obige Code tut, ist, ein (nützliches) InvalidCastExceptionin ein (nutzloses) zu verwandeln NullReferenceException.

Bin ich der einzige, der denkt, dass dies ein offensichtlicher Missbrauch des asSchlüsselworts ist? Oder habe ich etwas Offensichtliches verpasst und das obige Muster macht tatsächlich Sinn?

Heinzi
quelle
56
Wäre lustiger zu sehen (Kuss als S) .SteveIsSuchA (); Aber ich stimme zu, es ist ein Missbrauch.
SwDevMan81
5
Es ist viel cooler als zu schreiben ((T)x).SomeMethod(), nicht wahr? ;) (nur ein Scherz, du hast natürlich Recht!)
Lucero
8
@P Daddy Ich bin anderer Meinung, vollkommen gute Frage (macht dieses Codemuster tatsächlich Sinn) und sehr nützlich. +1 auf die Frage und ein Stirnrunzeln an alle, die für den Abschluss stimmen.
MarkJ
9
Lucerno hat recht, dieses Kodierungsmuster wird durch den Versuch induziert, Klammern zu vermeiden. Unheilbar, nachdem man Lisp ausgesetzt war.
Hans Passant
13
Optimierter Code : (f as T).SomeMethod();)
MSalters

Antworten:

252

Dein Verständnis ist wahr. Das klingt für mich nach einem Mikrooptimierungsversuch. Sie sollten eine normale Besetzung verwenden, wenn Sie sich des Typs sicher sind. Neben der Erzeugung einer vernünftigeren Ausnahme schlägt dies auch schnell fehl. Wenn Sie falsch über Ihre Annahme über die Art, wird Ihr Programm nicht sofort und Sie werden in der Lage sein , die Ursache des Fehlers zu sehen sofort , anstatt zu warten für eine NullReferenceExceptionoder ArgumentNullExceptionoder sogar einen logischen Fehler irgendwann in der Zukunft. Im Allgemeinen ist ein asAusdruck, auf den nullirgendwo keine Prüfung folgt, ein Codegeruch.

Wenn Sie sich bei der Besetzung nicht sicher sind und erwarten, dass sie fehlschlägt, sollten Sie sie asanstelle einer normalen Besetzung verwenden, die mit einem try-catchBlock umwickelt ist . Darüber hinaus wird die Verwendung von asüber eine Typprüfung gefolgt von einer Besetzung empfohlen. Anstatt:

if (x is SomeType)
   ((SomeType)x).SomeMethod();

Verwenden Sie Folgendes, um eine isinstAnweisung für das isSchlüsselwort und eine castclassAnweisung für die Besetzung zu generieren (wobei die Besetzung effektiv zweimal ausgeführt wird):

var v = x as SomeType;
if (v != null)
    v.SomeMethod();

Dies erzeugt nur eine isinstAnweisung. Die erstere Methode weist einen potenziellen Fehler in Multithread-Anwendungen auf, da eine Race-Bedingung dazu führen kann, dass die Variable ihren Typ ändert, nachdem die isPrüfung erfolgreich war und an der Cast-Linie fehlschlägt. Die letztere Methode ist für diesen Fehler nicht anfällig.


Die folgende Lösung wird für die Verwendung im Produktionscode nicht empfohlen . Wenn Sie solch ein grundlegendes Konstrukt in C # wirklich hassen, können Sie in Betracht ziehen, zu VB oder einer anderen Sprache zu wechseln.

Falls jemand die Besetzungssyntax verzweifelt hasst, kann er eine Erweiterungsmethode schreiben, um die Besetzung nachzuahmen:

public static T To<T>(this object o) { // Name it as you like: As, Cast, To, ...
    return (T)o;
}

und verwenden Sie eine ordentliche [?] Syntax:

obj.To<SomeType>().SomeMethod()
Mehrdad Afshari
quelle
5
Ich denke, die Rennbedingungen sind irrelevant. Wenn dieses Problem auftritt, ist Ihr Code nicht threadsicher und es gibt zuverlässigere Möglichkeiten, es zu lösen, als das Schlüsselwort "as" zu verwenden. +1 für den Rest der Antwort.
RMorrisey
10
@RMorrisey: Ich habe mindestens ein Beispiel im Sinn: Angenommen, Sie haben ein cacheObjekt, das ein anderer Thread versucht, es ungültig zu machen, indem er es auf setzt null. In sperrenfreien Szenarien können solche Dinge auftreten.
Mehrdad Afshari
10
is + cast reicht aus, um eine Warnung "Nicht unnötig umwandeln " von FxCop auszulösen : msdn.microsoft.com/en-us/library/ms182271.aspx Dies sollte Grund genug sein, das Konstrukt zu vermeiden.
David Schmitt
2
Sie sollten vermeiden, Erweiterungsmethoden zu aktivieren Object. Die Verwendung der Methode für einen Werttyp führt dazu, dass dieser unnötig eingerahmt wird.
MgSam
2
@MgSam Offensichtlich ist ein solcher Anwendungsfall für die ToMethode hier nicht sinnvoll , da er nur über die Vererbungshierarchie konvertiert, was für Werttypen ohnehin ein Boxen beinhaltet. Natürlich ist die gesamte Idee eher theoretisch als ernst.
Mehrdad Afshari
42

IMHO, asnur Sinn machen, wenn mit einem nullScheck kombiniert :

var y = x as T;
if (y != null)
    y.SomeMethod();
Rubens Farias
quelle
40

Bei Verwendung von 'as' werden keine benutzerdefinierten Conversions angewendet, während die Besetzung sie gegebenenfalls verwendet. Das kann in einigen Fällen ein wichtiger Unterschied sein.

Larry Fix
quelle
5
Dies ist wichtig zu beachten. Eric Lippert geht das hier durch: blogs.msdn.com/ericlippert/archive/2009/10/08/…
P Daddy
5
Netter Kommentar, P! Wenn Ihr Code jedoch von dieser Unterscheidung abhängt, würde ich sagen, dass es in Ihrer Zukunft eine nächtliche Debugging-Sitzung gibt.
TrueWill
36

Ich habe hier ein bisschen darüber geschrieben:

http://blogs.msdn.com/ericlippert/archive/2009/10/08/what-s-the-difference-between-as-and-cast-operators.aspx

Ich verstehe deine Meinung. Und ich stimme dem Kern zu: Ein Cast-Operator kommuniziert "Ich bin sicher, dass dieses Objekt in diesen Typ konvertiert werden kann, und ich bin bereit, eine Ausnahme zu riskieren, wenn ich falsch liege", während ein "As" -Operator kommuniziert "Ich bin nicht sicher, ob dieses Objekt in diesen Typ konvertiert werden kann. Geben Sie mir eine Null, wenn ich falsch liege."

Es gibt jedoch einen subtilen Unterschied. (x als T) .Was auch immer () kommuniziert "Ich weiß nicht nur, dass x in ein T konvertiert werden kann, sondern darüber hinaus, dass dies nur Referenz- oder Unboxing-Konvertierungen beinhaltet und dass x außerdem nicht null ist". Das kommuniziert andere Informationen als ((T) x) .Was auch immer (), und vielleicht ist es das, was der Autor des Codes beabsichtigt.

Eric Lippert
quelle
1
Ich bin nicht einverstanden mit Ihrer spekulativen Verteidigung des Autors des Codes in Ihrem letzten Satz. ((T)x).Whatever() auch in Verbindung steht , die xnicht ist , [bestimmt sein] NULL, und ich hoch Zweifel daran , dass ein Autor würde typischerweise egal , ob die Umwandlung zu Tnur mit Referenz- oder unboxing Umwandlungen stattfindet, oder wenn es erfordert eine benutzerdefinierte oder Darstellung ändernden Konvertierung. Wenn ich definiere public static explicit operator Foo(Bar b){}, dann ist es eindeutig meine Absicht, mit der Barich vereinbar bin Foo. Es ist selten, dass ich diese Konvertierung vermeiden möchte.
P Daddy
4
Nun, vielleicht würden die meisten Code-Autoren diese subtile Unterscheidung nicht treffen. Ich persönlich könnte es sein, aber wenn ich es wäre, würde ich einen Kommentar zu diesem Effekt hinzufügen.
Eric Lippert
16

Ich habe oft Verweise auf diesen irreführenden Artikel als Beweis dafür gesehen, dass "as" schneller ist als Casting.

Einer der offensichtlicheren irreführenden Aspekte dieses Artikels ist die Grafik, die nicht angibt, was gemessen wird: Ich vermute, dass fehlgeschlagene Casts gemessen werden (wobei "as" offensichtlich viel schneller ist, da keine Ausnahme ausgelöst wird).

Wenn Sie sich die Zeit nehmen, um die Messungen durchzuführen, werden Sie feststellen, dass das Casting erwartungsgemäß schneller ist als "as", wenn das Casting erfolgreich ist.

Ich vermute, dass dies ein Grund für die Verwendung des Schlüsselworts als Frachtkult anstelle einer Besetzung ist.

Joe
quelle
2
Danke für den Link, das ist sehr interessant. Von wie ich den Artikel verstanden, er tut das nicht-Ausnahmefall vergleichen. Trotzdem wurde der Artikel für .net 1.1 geschrieben, und die Kommentare weisen darauf hin, dass sich dies in .net 2.0 geändert hat: Die Leistung ist jetzt fast gleich, wobei die Präfixbesetzung sogar etwas schneller ist.
Heinzi
1
Der Artikel impliziert, dass er den Nicht-Ausnahmefall vergleicht, aber ich habe vor langer Zeit einige Tests durchgeführt und konnte seine behaupteten Ergebnisse selbst mit .NET 1.x nicht reproduzieren. Und da der Artikel nicht den Code enthält, der zum Ausführen des Benchmarks verwendet wird, ist es unmöglich zu sagen, was verglichen wird.
Joe
"Frachtkult" - perfekt. Die vollständigen Informationen finden Sie unter "Cargo Cult Science Richard Feynman".
Bob Denny
11

Die direkte Besetzung benötigt mehr Klammern als das asSchlüsselwort. Selbst wenn Sie sich zu 100% sicher sind, um welchen Typ es sich handelt, wird die visuelle Unordnung verringert.

Einverstanden über die Ausnahmesache. Aber zumindest für mich sind die meisten Verwendungszwecke von asBoil Down, um nulldanach zu prüfen , was ich schöner finde, als eine Ausnahme zu erwischen.

Joey
quelle
8

In 99% der Fälle, in denen ich "as" verwende, bin ich mir nicht sicher, was der tatsächliche Objekttyp ist

var x = obj as T;
if(x != null){
 //x was type T!
}

und ich möchte keine expliziten Besetzungsausnahmen abfangen oder zweimal mit "is" besetzen:

//I don't like this
if(obj is T){
  var x = (T)obj; 
}
Max Galkin
quelle
8
Sie haben gerade den richtigen Anwendungsfall für beschrieben as. Was ist die anderen 1%?
P Daddy
In einem Tippfehler? =) Ich meinte 99% der Zeit, dass ich genau dieses Code-Snippet verwende, während ich manchmal "as" in einem Methodenaufruf oder an einem anderen Ort verwenden kann.
Max Galkin
D'oh, und wie ist das weniger nützlich als die zweite populäre Antwort ???
Max Galkin
2
+1 Ich bin damit einverstanden, dass dies genauso wertvoll ist wie die Antwort von Rubens Farias - die Leute werden hoffentlich hierher kommen und dies wird ein nützliches Beispiel sein
Ruben Bartelink
8

Es ist nur, weil die Leute es mögen, wie es aussieht, es ist sehr lesbar.

Seien wir ehrlich: Der Casting- / Konvertierungsoperator in C-ähnlichen Sprachen ist in Bezug auf die Lesbarkeit ziemlich schrecklich. Mir würde es besser gefallen, wenn C # entweder die Javascript-Syntax von:

object o = 1;
int i = int(o);

Oder definieren Sie einen toOperator, dessen Casting-Äquivalent as:

object o = 1;
int i = o to int;
JulianR
quelle
Damit Sie wissen, ist die von Ihnen erwähnte JavaScript-Syntax auch in C ++ zulässig.
P Daddy
@PDaddy: Es ist jedoch keine direkte 100% kompatible alternative Syntax und ist nicht als solche gedacht (Operator X gegen Konvertierungskonstruktor)
Ruben Bartelink
Ich würde es vorziehen, die C ++ - Syntax von dynamic_cast<>()(und ähnlichem) zu verwenden. Du machst etwas Hässliches, es sollte hässlich aussehen.
Tom Hawtin - Tackline
5

Die Leute mögen es asso sehr, weil sie sich dadurch vor Ausnahmen sicher fühlen ... Wie eine Garantie auf eine Schachtel. Ein Mann gibt eine ausgefallene Garantie auf die Schachtel, weil er möchte, dass Sie sich im Inneren warm und wohlig fühlen. Sie glauben, Sie haben diese kleine Schachtel nachts unter Ihr Kissen gelegt, die Garantiefee könnte herunterkommen und ein Viertel verlassen, habe ich Recht, Ted?

Zurück zum Thema ... Wenn Sie eine direkte Besetzung verwenden, besteht die Möglichkeit einer ungültigen Besetzungsausnahme. Menschen wenden sich daher asals umfassende Lösung für alle ihre Casting-Anforderungen an, da sie as(für sich genommen) niemals eine Ausnahme auslösen werden. Aber das Lustige daran ist, dass Sie in dem Beispiel, das (x as T).SomeMethod();Sie gegeben haben, eine ungültige Besetzungsausnahme gegen eine Nullreferenzausnahme eintauschen. Was das eigentliche Problem verschleiert, wenn Sie die Ausnahme sehen.

Ich benutze im Allgemeinen nicht aszu viel. Ich bevorzuge den isTest, weil er mir lesbarer und sinnvoller erscheint, als eine Besetzung zu versuchen und nach Null zu suchen.

Bob
quelle
2
"Ich bevorzuge den is-Test" - "is", gefolgt von einer Umwandlung, ist natürlich langsamer als "as", gefolgt von einem Test für null (genau wie "IDictionary.ContainsKey", gefolgt von einer Dereferenzierung mit dem Indexer, langsamer als "IDictionary.TryGetValue" "). Aber wenn Sie es besser lesbar finden, ist der Unterschied zweifellos selten signifikant.
Joe
Die wichtige Aussage im mittleren Teil ist, wie sich Menschen asals pauschale Lösung bewerben, weil sie sich dadurch sicher fühlen.
Bob
5

Dies muss einer meiner Top-Ärmel sein .

In Stroustrups D & E und / oder einem Blog-Beitrag, den ich derzeit nicht finden kann, wird der Begriff eines toOperators erörtert, der den von https://stackoverflow.com/users/73070/johannes-rossel gemachten Punkt ansprechen würde (dh dieselbe Syntax wie, asaber mitDirectCast Semantik) ).

Der Grund, warum dies nicht implementiert wurde, ist, dass ein Cast Schmerzen verursachen und hässlich sein sollte, sodass Sie davon abgehalten werden, ihn zu verwenden.

Schade, dass "clevere" Programmierer (oft Buchautoren (Juval Lowy IIRC)) dies umgehen, indem sie asauf diese Weise missbrauchen (C ++ bietet aswahrscheinlich aus diesem Grund keine an ).

Auch hat VB mehr Konsistenz eine einheitliche Syntax gekennzeichnet, dass zwingt Sie ein wählen , TryCastoder DirectCastund Make up your mind !

Ruben Bartelink
quelle
+1. Sie meinten wahrscheinlich DirectCast Verhalten , nicht Syntax .
Heinzi
@ Heinzi: Ta für +1. Guter Punkt. Beschlossen, ein Smartarse zu sein und semanticsstattdessen zu verwenden: P
Ruben Bartelink
Da C # keinen Anspruch auf Kompatibilität mit C, C ++ oder Java hat, bin ich verärgert über einige Dinge, die es aus diesen Sprachen entlehnt. Es geht über "Ich weiß, dass dies ein X ist" und "Ich weiß, dass dies kein X ist, sondern als eins dargestellt werden kann" bis zu "Ich weiß, dass dies kein X ist und möglicherweise nicht wirklich als eins dargestellt werden kann" , aber gib mir trotzdem ein X. " Ich konnte Nützlichkeit für eine double-to-intBesetzung erkennen, die fehlschlagen würde, wenn doublesie keinen exakten Wert darstellt, der in eine passen könnte Int32, aber (int)-1.5Ausbeute -1 zu haben, ist einfach hässlich.
Supercat
@supercat Ja, aber das Sprachdesign ist nicht einfach, wie wir alle wissen - sehen Sie sich die Kompromisse an, die mit C # -Nullables verbunden sind. Das einzige bekannte Gegenmittel ist das regelmäßige Lesen von C # in Depth-Editionen, wenn sie erscheinen :) Zum Glück geht es mir heutzutage mehr darum, die Nuancen von F # zu verstehen, und es ist in vielen dieser Fragen viel vernünftiger.
Ruben Bartelink
@RubenBartelink: Ich bin mir nicht ganz sicher, welche genauen Probleme die nullbaren Typen lösen sollten, aber ich würde in den meisten Fällen denken, dass es schöner gewesen wäre, ein MaybeValid<T>mit zwei öffentlichen Feldern zu haben IsValidund Valuewelchen Code es nach eigenem Ermessen tun könnte. Das hätte zB erlaubt MaybeValid<TValue> TryGetValue(TKey key) { var ret = default(MaybeValid<TValue>); ret.IsValid = dict.TryGetValue(key, out ret.Value); return ret; }. Dies würde nicht nur mindestens zwei Kopiervorgänge im Vergleich zu sparen Nullable<T>, sondern es könnte sich auch für jeden Typ Tlohnen - nicht nur für Klassen.
Supercat
2

Ich glaube, dass das asSchlüsselwort als eine elegantere Version von dynamic_castaus C ++ angesehen werden könnte.

Andrew Garrison
quelle
Ich würde sagen, eine direkte Besetzung in C # ähnelt eher dynamic_castC ++.
P Daddy
Ich denke, die direkte Umwandlung in C # entspricht eher der statischen Besetzung in C ++.
Andrew Garrison
2
@Ruben Bartelink: Es wird nur null mit Zeigern zurückgegeben. Mit Referenzen, die Sie nach Möglichkeit verwenden sollten, wird geworfen std::bad_cast.
P Daddy
1
@ Andrew Garrison: Führt static_castkeine Laufzeitüberprüfung durch. In C # gibt es keine ähnliche Besetzung.
P Daddy
Leider wusste ich nicht, dass Sie die Abgüsse für Referenzen verwenden können, da ich sie immer nur für Zeiger verwendet habe, aber P Daddy ist absolut korrekt!
Andrew Garrison
1

Es ist wahrscheinlich ohne technischen Grund beliebter, sondern nur, weil es einfacher zu lesen und intuitiver ist. (Nicht zu sagen, dass es besser ist, nur zu versuchen, die Frage zu beantworten)

Jla
quelle
1

Ein Grund für die Verwendung von "as":

T t = obj as T;
 //some other thread changes obj to another type...
if (t != null) action(t); //still works

Anstelle von (schlechter Code):

if (obj is T)
{
     //bang, some other thread changes obj to another type...
     action((T)obj); //InvalidCastException
}
Rauhotz
quelle
2
Wenn Sie so hässliche Rennbedingungen haben, haben Sie größere Probleme (aber stimmen Sie zu, dass es eine schöne Probe ist, um mit den anderen zu gehen, also +1
Ruben Bartelink
-1, da dies einen Irrtum aufrechterhält. Wenn andere Threads den Typ von obj ändern können, haben Sie immer noch Probleme. Die Behauptung "// funktioniert immer noch" ist sehr unwahrscheinlich, da t als Zeiger auf T verwendet wird, aber auf einen Speicher verweist, der kein T mehr ist. Keine der beiden Lösungen funktioniert, wenn der andere Thread den Typ von ändert obj während der Aktion (t) ausgeführt wird.
Stephen C. Steel
5
@ Stephen C. Steel: Sie scheinen ziemlich verwirrt zu sein. Das Ändern des Typs von objwürde bedeuten, die objVariable selbst so zu ändern , dass sie einen Verweis auf ein anderes Objekt enthält. Es würde den Inhalt des Speichers nicht ändern, in dem sich das Objekt befindet, auf das ursprünglich verwiesen wurde obj. Dieses ursprüngliche Objekt würde unverändert bleiben und die tVariable würde immer noch einen Verweis darauf enthalten.
P Daddy
1
@P Daddy - Ich denke, Sie haben Recht, und ich habe mich geirrt: Wenn obj von einem T-Objekt zu einem T2-Objekt zurückgeworfen würde, würde t immer noch auf das alte T-Objekt zeigen. Da t immer noch auf das alte Objekt verweist, kann kein Müll gesammelt werden, sodass das alte T-Objekt gültig bleibt. Meine Rennzustandsdetektorschaltungen wurden auf C ++ trainiert, wobei ähnlicher Code mit dynamic_cast ein potenzielles Problem darstellen würde.
Stephen C. Steel