Est-il judicieux d'utiliser «comme» au lieu d'un transtypage même s'il n'y a pas de vérification nulle? [fermé]

351

Dans les blogs de développement, les exemples de code en ligne et (récemment) même un livre, je continue de trébucher sur le code comme ceci:

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

ou pire encore:

(x as T).SomeMethod();

Cela n'a aucun sens pour moi. Si vous êtes sûr xest de type T, vous devez utiliser une distribution directe: (T)x. Si vous n'êtes pas sûr, vous pouvez utiliser asmais devez vérifier nullavant d'effectuer une opération. Tout ce que fait le code ci-dessus est de transformer un (utile) InvalidCastExceptionen (inutile) NullReferenceException.

Suis-je le seul à penser que c'est un abus flagrant du asmot-clé? Ou ai-je raté quelque chose d'évident et le schéma ci-dessus a-t-il du sens?

Heinzi
la source
56
Serait plus drôle de voir (embrasser comme S) .SteveIsSuchA (); Mais je suis d'accord, c'est un abus.
SwDevMan81
5
C'est beaucoup plus cool que d'écrire ((T)x).SomeMethod(), non? ;) (je plaisante, vous avez raison bien sûr!)
Lucero
8
@P papa, je ne suis pas d'accord, c'est une très bonne question (ce modèle de code a-t-il un sens) et très utile. +1 à la question et un froncement de sourcils à quiconque vote pour fermer.
MarkJ
9
Lucerno a raison, ce schéma de codage est induit en essayant d'éviter les parenthèses. Incurable après avoir été exposé à Lisp.
Hans Passant
13
Code optimisé (f as T).SomeMethod()
:;

Réponses:

252

Votre compréhension est vraie. Cela ressemble à essayer de micro-optimiser pour moi. Vous devez utiliser un casting normal lorsque vous êtes sûr du type. En plus de générer une exception plus sensible, il échoue également rapidement. Si vous vous trompez sur votre hypothèse sur le type, votre programme échouera immédiatement et vous pourrez voir la cause de l'échec immédiatement plutôt que d'attendre une NullReferenceExceptionou ArgumentNullExceptionou même une erreur logique dans le futur. En général, une asexpression qui n'est pas suivie d'une nullvérification quelque part est une odeur de code.

D'un autre côté, si vous n'êtes pas sûr de la distribution et que vous vous attendez à ce qu'elle échoue, vous devez utiliser à la asplace d'une distribution normale enveloppée d'un try-catchbloc. De plus, l'utilisation de asest recommandée par rapport à une vérification de type suivie d'un plâtre. Au lieu de:

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

qui génère une isinstinstruction pour le ismot - clé et une castclassinstruction pour le casting (exécutant effectivement le casting deux fois), vous devez utiliser:

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

Cela ne génère qu'une isinstinstruction. La première méthode présente un défaut potentiel dans les applications multithread car une condition de concurrence peut entraîner le changement de type de la variable après la isréussite de la vérification et l'échec de la ligne de distribution. Cette dernière méthode n'est pas sujette à cette erreur.


La solution suivante n'est pas recommandée pour une utilisation dans le code de production. Si vous détestez vraiment une construction aussi fondamentale en C #, vous pourriez envisager de passer à VB ou à un autre langage.

Dans le cas où l'on déteste désespérément la syntaxe du cast, il / elle peut écrire une méthode d'extension pour imiter le cast:

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

et utilisez une syntaxe [?] soignée:

obj.To<SomeType>().SomeMethod()
Mehrdad Afshari
la source
5
Je pense que la condition de concurrence n'est pas pertinente. Si vous rencontrez ce problème, votre code n'est pas adapté aux threads et il existe des moyens plus fiables de le résoudre qu'en utilisant le mot clé "as". +1 pour le reste de la réponse.
RMorrisey
10
@RMorrisey: J'ai au moins un exemple en tête: Supposons que vous ayez un cacheobjet qu'un autre thread essaie de invalider en le définissant sur null. Dans les scénarios sans verrouillage, ce genre de choses peut survenir.
Mehrdad Afshari
10
is + cast suffit pour déclencher un avertissement "Ne pas transtyper inutilement" de FxCop: msdn.microsoft.com/en-us/library/ms182271.aspx Cela devrait être une raison suffisante pour éviter la construction.
David Schmitt
2
Vous devez éviter d'activer des méthodes d'extension Object. L'utilisation de la méthode sur un type de valeur entraînera sa mise en boîte inutilement.
MgSam
2
@MgSam Évidemment, un tel cas d'utilisation n'a pas de sens pour la Tométhode ici, car il ne se convertit qu'à travers la hiérarchie d'héritage, qui pour les types de valeur implique de toute façon la boxe. Bien sûr, l'idée dans son ensemble est plus théorique que sérieuse.
Mehrdad Afshari
42

À mon humble avis, il assuffit d'avoir du sens lorsqu'il est combiné avec un nullchèque:

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

L'utilisation de "as" n'applique pas les conversions définies par l'utilisateur, tandis que la distribution les utilisera le cas échéant. Cela peut être une différence importante dans certains cas.

Larry Fix
la source
5
C'est important à retenir. Eric Lippert en parle
P Daddy
5
Joli commentaire, P! Si votre code dépend de cette distinction, je dirais qu'il y aura une session de débogage tard dans la nuit dans votre avenir.
TrueWill
36

J'ai écrit un peu à ce sujet ici:

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

Je comprends votre point. Et je suis d'accord avec l'idée maîtresse: qu'un opérateur de cast communique "Je suis sûr que cet objet peut être converti en ce type, et je suis prêt à risquer une exception si je me trompe", alors qu'un opérateur "as" communique "Je ne suis pas sûr que cet objet puisse être converti dans ce type; donnez-moi une valeur nulle si je me trompe".

Cependant, il existe une différence subtile. (x as T). Quoi que () communique "Je sais non seulement que x peut être converti en T, mais en plus, cela implique seulement des conversions de référence ou de déballage, et en plus, que x n'est pas nul". Cela communique des informations différentes de ((T) x). Quoi que (), et c'est peut-être ce que l'auteur du code a l'intention de faire.

Eric Lippert
la source
1
Je ne suis pas d'accord avec votre défense spéculative de l'auteur du code dans votre dernière phrase. communique ((T)x).Whatever() également qui xn'est pas [censé être] nul, et je doute fortement qu'un auteur se soucie généralement de savoir si la conversion se Tproduit uniquement avec des conversions de référence ou de décodage, ou si elle nécessite une conversion définie par l'utilisateur ou qui change la représentation. Après tout, si je le définis public static explicit operator Foo(Bar b){}, c'est clairement mon intention qui doit Barêtre considérée comme compatible avec Foo. Il est rare que je veuille éviter cette conversion.
P Daddy
4
Eh bien, peut-être que la plupart des auteurs de code ne feraient pas cette distinction subtile. Personnellement, je pourrais l'être, mais si je l'étais, j'ajouterais un commentaire à cet effet.
Eric Lippert
16

J'ai souvent vu des références à cet article trompeur comme une preuve que "as" est plus rapide que le casting.

L'un des aspects trompeurs les plus évidents de cet article est le graphique, qui n'indique pas ce qui est mesuré: je soupçonne qu'il mesure les transformations échouées (où "as" est évidemment beaucoup plus rapide car aucune exception n'est levée).

Si vous prenez le temps de faire les mesures, vous verrez que le lancer est, comme vous vous en doutez, plus rapide que "as" lorsque le lancer réussit.

Je soupçonne que cela peut être l'une des raisons de l'utilisation du mot-clé as par "culte du fret" au lieu d'un casting.

Joe
la source
2
Merci pour le lien, c'est très intéressant. De la façon dont je compris l'article, il ne compare le cas de non-exception. Néanmoins, l'article a été écrit pour .net 1.1, et les commentaires indiquent que cela a changé dans .net 2.0: les performances sont désormais presque égales, le préfixe étant même légèrement plus rapide.
Heinzi
1
L'article implique qu'il compare le cas de non-exception, mais j'ai fait quelques tests il y a longtemps et je n'ai pas pu reproduire ses résultats revendiqués, même avec .NET 1.x. Et puisque l'article ne fournit pas le code utilisé pour exécuter le test, il est impossible de dire ce qui est comparé.
Joe
"Cargo culte" - parfait. Consultez "Cargo Cult Science Richard Feynman" pour plus d'informations.
Bob Denny
11

Le casting direct a besoin d'une paire de parenthèses de plus que le asmot clé. Ainsi, même dans le cas où vous êtes sûr à 100% du type, cela réduit l'encombrement visuel.

Mais je suis d'accord sur l'exception. Mais au moins pour moi, la plupart des utilisations de l' asébullition se vérifient par la nullsuite, ce que je trouve plus agréable que d'attraper une exception.

Joey
la source
8

99% du temps quand j'utilise "comme" c'est quand je ne suis pas sûr quel est le type d'objet réel

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

et je ne veux pas intercepter les exceptions de cast explicites ni faire de cast deux fois, en utilisant "is":

//I don't like this
if(obj is T){
  var x = (T)obj; 
}
Max Galkin
la source
8
Vous venez de décrire le cas d'utilisation approprié pour as. Quel est l'autre 1%?
P Daddy
Dans une faute de frappe? =) Je voulais dire 99% du temps que j'utilise cet extrait de code exact , alors que parfois je peux utiliser "as" dans un appel de méthode ou ailleurs.
Max Galkin
D'oh, et comment est-ce moins utile que la deuxième réponse populaire ???
Max Galkin
2
+1 Je suis d'accord pour dire que cela est aussi précieux que la réponse de Rubens Farias - j'espère que les gens viendront ici et ce sera un exemple utile
Ruben Bartelink
8

C'est juste parce que les gens aiment son apparence, c'est très lisible.

Avouons-le: l'opérateur de conversion / conversion dans les langages de type C est assez terrible, en termes de lisibilité. J'aimerais mieux que C # adopte la syntaxe Javascript de:

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

Ou définissez un toopérateur, l'équivalent de casting de as:

object o = 1;
int i = o to int;
JulianR
la source
Pour information, la syntaxe JavaScript que vous mentionnez est également autorisée en C ++.
P Daddy, du
@PDaddy: Ce n'est pas une syntaxe alternative directement compatible à 100% et n'est pas conçu comme cela (opérateur X vs constructeur de conversion)
Ruben Bartelink
Je préférerais qu'il utilise la syntaxe C ++ de dynamic_cast<>()(et similaire). Vous faites quelque chose de laid, cela devrait paraître laid.
Tom Hawtin - tackline
5

Les gens aiment astellement parce que cela les met à l'abri des exceptions ... Comme une garantie sur une boîte. Un gars met une garantie de fantaisie sur la boîte parce qu'il veut que vous vous sentiez tout chaud et grillé à l'intérieur. Vous pensez que vous avez mis cette petite boîte sous votre oreiller la nuit, la fée de garantie pourrait descendre et quitter un quart, ai-je raison Ted?

Retour sur le sujet ... lors de l'utilisation d'un cast direct, il existe la possibilité d'une exception de cast non valide. Ainsi, les gens postulent en astant que solution globale à tous leurs besoins en matière de casting, car as(en eux-mêmes) ne lèveront jamais d'exception. Mais ce qui est drôle, c'est que dans l'exemple que vous avez donné, (x as T).SomeMethod();vous échangez une exception de distribution non valide contre une exception de référence nulle. Ce qui obscurcit le vrai problème lorsque vous voyez l'exception.

Je n'en utilise généralement pas astrop. Je préfère le istest car il me semble plus lisible et plus logique que d'essayer un cast et de vérifier null.

Bob
la source
2
"Je préfère le test is" - "is" suivi d'un transtypage est bien sûr plus lent que "as" suivi d'un test pour null (tout comme "IDictionary.ContainsKey" suivi d'un déréférencement à l'aide de l'indexeur est plus lent que "IDictionary.TryGetValue "). Mais si vous le trouvez plus lisible, la différence est sans doute rarement significative.
Joe
L'énoncé important dans la partie centrale est de savoir comment les gens présentent asune demande globale, car ils se sentent en sécurité.
Bob
5

Cela doit être l'un de mes coups de cœur .

Le D&E de Stroustrup et / ou un article de blog que je ne peux pas trouver en ce moment traite de la notion d'un toopérateur qui répondrait au point soulevé par https://stackoverflow.com/users/73070/johannes-rossel (c'est-à-dire, même syntaxe que asmais avec la DirectCastsémantique ).

La raison pour laquelle cela n'a pas été implémenté est parce qu'un casting devrait causer de la douleur et être laid, donc vous êtes poussé à ne pas l'utiliser.

Dommage que des programmeurs «intelligents» (souvent des auteurs de livres (Juval Lowy IIRC)) contournent cela en abusant asde cette façon (C ++ n'en offre pas as, probablement pour cette raison).

Même VB a une plus grande cohérence d'avoir une syntaxe uniforme qui vous oblige à choisir un TryCastou DirectCastet faites votre esprit !

Ruben Bartelink
la source
+1. Vous vouliez probablement parler de DirectCast comportement , pas de syntaxe .
Heinzi
@Heinzi: Ta pour +1. Bon point. A décidé d'être un smartarse et d'utiliser à la semanticsplace: P
Ruben Bartelink
Étant donné que C # n'a aucune prétention de compatibilité avec C, C ++ ou Java, je me retrouve irrité à certaines des choses qu'il emprunte à ces langages. Cela va au-delà de "Je sais que c'est un X", et "Je sais que ce n'est pas un X, mais peut être représenté comme un", à "Je sais que ce n'est pas un X, et peut-être pas vraiment représentable comme un seul" , mais donnez-moi quand même un X. " Je pouvais voir l'utilité d'un double-to-intcast qui échouerait si le doublene représentait pas une valeur exacte qui pourrait tenir dans un Int32, mais avoir un (int)-1.5rendement -1 est tout simplement moche.
supercat
@supercat Oui, mais la conception du langage n'est pas aussi simple que nous le savons tous - regardez l'ensemble des compromis impliqués dans les nullables C #. Le seul antidote connu est la lecture régulière des éditions de C # in Depth au fur et à mesure qu'elles arrivent :) Heureusement, je suis plus soucieux de comprendre les nuances de F # ces jours-ci et c'est beaucoup plus sain d'esprit autour de beaucoup de ces questions.
Ruben Bartelink
@ RubenBartelink: Je ne sais pas exactement quels problèmes exacts les types nullables étaient censés résoudre, mais je pense que dans la plupart des cas, il aurait été plus agréable d'avoir un MaybeValid<T>avec deux champs publics IsValidet Valuequel code pourrait faire comme bon lui semble. Cela aurait permis par exemple MaybeValid<TValue> TryGetValue(TKey key) { var ret = default(MaybeValid<TValue>); ret.IsValid = dict.TryGetValue(key, out ret.Value); return ret; }. Non seulement cela permettrait d'économiser au moins deux opérations de copie par rapport à Nullable<T>, mais cela pourrait également valoir pour n'importe quel type - Tpas seulement les classes.
supercat
2

Je crois que le asmot - clé pourrait être considéré comme une version plus élégante dynamic_castdu C ++.

Andrew Garrison
la source
Je dirais qu'une distribution directe en C # ressemble plus dynamic_castà C ++.
P Daddy
je pense que la distribution directe en C # est plus équivalente à la static_cast en C ++.
Andrew Garrison
2
@Ruben Bartelink: Il ne renvoie que null avec des pointeurs. Avec des références, que vous devriez utiliser lorsque cela est possible, il lance std::bad_cast.
P Daddy
1
@Andrew Garrison: n'effectue static_castaucune vérification de type d'exécution. Il n'y a pas de distribution similaire à cela en C #.
P Daddy
Malheureusement, je ne savais pas que vous pouviez même utiliser les transtypages sur les références, car je ne les ai utilisés que sur des pointeurs, mais P Daddy est absolument correct!
Andrew Garrison du
1

Il est probablement plus populaire sans raison technique, mais simplement parce qu'il est plus facile à lire et plus intuitif. (Ne pas dire que cela améliore, juste essayer de répondre à la question)

Jla
la source
1

Une raison d'utiliser "as":

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

Au lieu de (mauvais code):

if (obj is T)
{
     //bang, some other thread changes obj to another type...
     action((T)obj); //InvalidCastException
}
Rauhotz
la source
2
Si vous avez des conditions de course cet uglym, vous avez de plus gros problèmes (mais convenez que c'est un bon échantillon pour aller avec les autres, donc +1
Ruben Bartelink
-1 car cela perpétue une erreur. Si d'autres threads peuvent changer le type d'obj, vous avez toujours des problèmes. Il est très peu probable que la revendication "// fonctionne toujours" soit vraie, car t sera utilisé comme pointeur vers T, mais elle pointe vers la mémoire qui n'est plus un T. Aucune des deux solutions ne fonctionnera lorsque l'autre thread changera le type de obj pendant que l'action (t) est en cours.
Stephen C. Steel
5
@Stephen C. Steel: Vous semblez assez confus. Changer le type de objsignifierait changer la objvariable elle-même pour contenir une référence à un autre objet. Il ne modifierait pas le contenu de la mémoire à laquelle réside l'objet référencé à l'origine par obj. Cet objet d'origine resterait inchangé et la tvariable contiendrait toujours une référence à lui.
P Daddy
1
@P Daddy - Je pense que vous avez raison, et je me trompais: si obj rebondissait d'un objet T vers un objet T2, alors t serait toujours pointé vers l'ancien objet T. Puisque t fait toujours référence à l'ancien objet, il ne peut pas être récupéré, donc l'ancien objet T restera valide. Mes circuits de détection de conditions de compétition ont été formés sur C ++, où un code similaire utilisant dynamic_cast serait un problème potentiel.
Stephen C. Steel du