Casting direct vs opérateur 'as'?

710

Considérez le code suivant:

void Handler(object o, EventArgs e)
{
   // I swear o is a string
   string s = (string)o; // 1
   //-OR-
   string s = o as string; // 2
   // -OR-
   string s = o.ToString(); // 3
}

Quelle est la différence entre les trois types de casting (d'accord, le 3ème n'est pas un casting, mais vous obtenez l'intention). Lequel devrait être préféré?

nullDev
la source
1
Pas tout à fait un doublon, mais il y a aussi quelques discussions sur les performances dans une question précédente .
découpé le
8
4ème string s = Convert.ToString(o):; 5ème: string s = $"{o}"(ou de manière équivalente le string.Formatformulaire pour le C # précédent)
Earth Engine

Réponses:

834
string s = (string)o; // 1

Lève InvalidCastException si on'est pas un string. Sinon, assigne oà s, même s'il l' oest null.

string s = o as string; // 2

Attribue nullà ssi on'est pas un stringou si oest null. Pour cette raison, vous ne pouvez pas l'utiliser avec des types de valeur (l'opérateur ne pourra jamais retourner nulldans ce cas). Sinon, attribue oà s.

string s = o.ToString(); // 3

Provoque une exception NullReferenceException si oest null. Attribue tout ce qui o.ToString()retourne s, quel que soit le type o.


Utilisez 1 pour la plupart des conversions - c'est simple et direct. J'ai tendance à ne presque jamais utiliser 2 car si quelque chose n'est pas du bon type, je m'attends généralement à ce qu'une exception se produise. Je n'ai vu qu'un besoin pour ce type de fonctionnalité de retour nul avec des bibliothèques mal conçues qui utilisent des codes d'erreur (par exemple, retourner null = erreur, au lieu d'utiliser des exceptions).

3 n'est pas un transtypage et n'est qu'un appel de méthode. Utilisez-le lorsque vous avez besoin de la représentation sous forme de chaîne d'un objet non-chaîne.

Sander
la source
2
Vous pouvez attribuer 'null' aux types de valeurs lorsqu'ils sont explicitement définis, par exemple: int? je; chaîne s = "5"; i = s comme int; // i est maintenant 5 s = null; i = s comme int; // i est maintenant nul
Anheledir
3
RE: Anheledir En fait, je serais nul après le premier appel. Vous devez utiliser une fonction de conversion explicite pour obtenir la valeur d'une chaîne.
Guvante
45
RE: Sander En fait, il existe une autre très bonne raison d'utiliser car, cela simplifie votre code de vérification (vérifier la null plutôt que de vérifier le type nul et correct) .Ceci est utile car la plupart du temps, vous préférez lever une exception personnalisée. Mais il est très vrai que les aveugles comme les appels sont mauvais.
Guvante
5
# 2 est pratique pour des choses comme les méthodes Equals où vous ne connaissez pas le type d'entrée. En général, oui, 1 serait préférable. Bien que préféré à cela, il serait évident d'utiliser le système de type pour se limiter à un type lorsque vous n'en attendez qu'un :)
Calum
6
# 2 est également utile lorsque vous avez du code qui pourrait faire quelque chose de spécifique pour un type spécialisé mais qui ne ferait rien autrement.
AnthonyWJones le
349
  1. string s = (string)o;À utiliser lorsque quelque chose devrait être définitivement l'autre chose.
  2. string s = o as string;À utiliser lorsque quelque chose pourrait être l'autre chose.
  3. string s = o.ToString(); À utiliser lorsque vous ne vous souciez pas de ce que c'est mais que vous souhaitez simplement utiliser la représentation de chaîne disponible.
Quibblesome
la source
1
J'ai l'impression que cette réponse semble bonne, mais elle n'est peut-être pas exacte.
j riv
1
J'aime les deux premiers, mais j'ajouterais "et vous êtes sûr qu'il n'est pas nul" à la troisième option.
Uxonith
2
vous pouvez utiliser Elvis (?.) ces jours-ci pour éviter d'avoir à vous en préoccuper: obj? .ToString ()
Quibblesome
@Quibblesome - excellente réponse mais j'ai dû m'arrêter pour penser à votre rebuttle! il me souffle littéralement que la langue existe depuis plus de 15 ans. C'est comme hier quand nous étions tous "énervés" essayant de convaincre les développeurs seniors de passer au C #.
Griswald_911
1
@Quibblesome belle réponse: allez-vous être ennuyé si j'ajoute ce que sont 1/2/3 afin qu'il ne soit pas nécessaire de faire défiler jusqu'à OP. Avec SO, je classerais les anciennes réponses en fonction des votes!
whytheq
29

Cela dépend vraiment si vous savez si oc'est une chaîne et ce que vous voulez en faire. Si votre commentaire signifie qu'il os'agit vraiment d'une chaîne, je préfère la (string)odistribution directe - il est peu probable qu'elle échoue.

Le plus grand avantage de l'utilisation de la distribution directe est qu'en cas d'échec, vous obtenez une InvalidCastException , qui vous indique à peu près ce qui n'a pas fonctionné.

Avec l' asopérateur, si on'est pas une chaîne, sest défini sur null, ce qui est pratique si vous n'êtes pas sûr et que vous souhaitez tester s:

string s = o as string;
if ( s == null )
{
    // well that's not good!
    gotoPlanB();
}

Cependant, si vous n'effectuez pas ce test, vous l'utiliserez splus tard et une exception NullReferenceException sera levée. Celles-ci ont tendance à être plus courantes et beaucoup plus difficiles à retrouver une fois qu'elles se produisent dans la nature, car presque chaque ligne déréférence une variable et peut en lancer une. D'un autre côté, si vous essayez de transtyper en un type de valeur (toute primitive ou structure telle que DateTime ), vous devez utiliser le transtypage direct - le asne fonctionnera pas.

Dans le cas particulier de la conversion en chaîne, chaque objet a un ToString, donc votre troisième méthode peut être correcte si elle on'est pas nulle et vous pensez que la ToStringméthode pourrait faire ce que vous voulez.

Blair Conrad
la source
2
Une remarque - vous pouvez utiliser asavec des types de valeur nullable . IE o as DateTimene fonctionnera pas, mais o as DateTime?...
John Gibb
Pourquoi ne pas utiliser à la if (s is string)place?
BornToCode
1
@BornToCode, pour moi, une préférence largement personnelle. En fonction de ce que vous faites, souvent après ising, vous devrez de nouveau lancer de toute façon, vous aurez donc le cast puis le hard cast. Pour une raison quelconque, le aschèque et null me semblait mieux.
Blair Conrad
9

Si vous savez déjà dans quel type il peut être casté, utilisez un cast de style C:

var o = (string) iKnowThisIsAString; 

Notez que ce n'est qu'avec une conversion de style C que vous pouvez effectuer une coercition de type explicite.

Si vous ne savez pas si c'est le type souhaité et que vous allez l'utiliser si c'est le cas, utilisez comme mot-clé:

var s = o as string;
if (s != null) return s.Replace("_","-");

//or for early return:
if (s==null) return;

Notez que as n'appellera aucun opérateur de conversion de type. Il ne sera pas non nul si l'objet n'est pas nul et nativement du type spécifié.

Utilisez ToString () pour obtenir une représentation sous forme de chaîne lisible par l'homme de tout objet, même s'il ne peut pas être converti en chaîne.

Mark Cidade
la source
3
C'est un petit problème intéressant concernant les opérateurs de conversion de type. J'ai quelques types pour lesquels j'ai créé des conversions, alors faites attention à cela.
AnthonyWJones
7

Le mot clé as est bon dans asp.net lorsque vous utilisez la méthode FindControl.

Hyperlink link = this.FindControl("linkid") as Hyperlink;
if (link != null)
{
     ...
}

Cela signifie que vous pouvez opérer sur la variable typée plutôt que de la convertir ensuite objectcomme vous le feriez avec une conversion directe:

object linkObj = this.FindControl("linkid");
if (link != null)
{
     Hyperlink link = (Hyperlink)linkObj;
}

Ce n'est pas énorme, mais cela permet d'économiser des lignes de code et d'affectation de variables, en plus c'est plus lisible

Glenn Slaven
la source
6

'as' est basé sur 'is', qui est un mot clé qui vérifie au moment de l'exécution si l'objet est compatible polimorphiquement (essentiellement si un transtypage peut être effectué) et retourne null si la vérification échoue.

Ces deux sont équivalents:

Utilisation de 'as':

string s = o as string;

Utiliser «est»:

if(o is string) 
    s = o;
else
    s = null;

Au contraire, la conversion de style c est également effectuée lors de l'exécution, mais lève une exception si la conversion ne peut pas être effectuée.

Juste pour ajouter un fait important:

Le mot clé 'as' ne fonctionne qu'avec les types de référence. Tu ne peux pas faire:

// I swear i is an int
int number = i as int;

Dans ces cas, vous devez utiliser le casting.

Sergio Acosta
la source
Merci d'avoir signalé mon erreur, vous avez raison. J'ai édité la réponse. Oops désolé.
Sergio Acosta
5

2 est utile pour convertir en un type dérivé.

Supposons qu'un est un animal:

b = a as Badger;
c = a as Cow;

if (b != null)
   b.EatSnails();
else if (c != null)
   c.EatGrass();

obtiendra un fed avec un minimum de lancers.

Joel à Gö
la source
2
@Chirs Moutray, ce n'est pas toujours possible, surtout s'il s'agit d'une bibliothèque.
deceleratedcaviar
5

Selon les expériences effectuées sur cette page: http://www.dotnetguru2.org/sebastienros/index.php/2006/02/24/cast_vs_as

(cette page affiche parfois des erreurs de "référent illégal", alors actualisez-le si c'est le cas)

La conclusion est que l'opérateur "as" est normalement plus rapide qu'un cast. Parfois beaucoup plus vite, parfois à peine plus vite.

Personnellement, je pense que "as" est aussi plus lisible.

Donc, comme il est à la fois plus rapide et "plus sûr" (ne lancera pas d'exception), et peut-être plus facile à lire, je recommande d'utiliser "as" tout le temps.

Brady Moritz
la source
4

"(string) o" entraînera une InvalidCastException car il n'y a pas de transtypage direct.

"o as string" aura pour résultat que s sera une référence nulle, plutôt qu'une exception levée.

"o.ToString ()" n'est pas un transtypage en soi, c'est une méthode qui est implémentée par objet, et donc d'une manière ou d'une autre, par chaque classe de .net qui "fait quelque chose" avec l'instance de la classe sur laquelle il est appelé et renvoie une chaîne.

N'oubliez pas que pour la conversion en chaîne, il y a aussi Convert.ToString (someType instanceOfThatType) où someType fait partie d'un ensemble de types, essentiellement les types de base des frameworks.

Rob
la source
3

Toutes les réponses données sont bonnes, si je peux ajouter quelque chose: pour utiliser directement les méthodes et les propriétés de la chaîne (par exemple ToLower), vous ne pouvez pas écrire:

(string)o.ToLower(); // won't compile

vous ne pouvez écrire que:

((string)o).ToLower();

mais vous pourriez écrire à la place:

(o as string).ToLower();

L' asoption est plus lisible (du moins à mon avis).

BornToCode
la source
la construction (o as string) .ToLower () annule le but de l'opérateur as. Cela lèvera une exception de référence nulle lorsque o ne peut pas être converti en chaîne.
james
@james - Mais qui a dit que le seul but de l'opérateur as était de lever une exception si le cast échouait? Si vous savez que o est une chaîne et que vous souhaitez simplement écrire du code plus propre, vous pouvez utiliser à la (o as string).ToLower()place des multiples crochets déroutants.
BornToCode
l'objectif de as est tout à fait le contraire - il ne doit pas lever l'exception lorsque le transtypage échoue, il doit retourner null. Disons que votre o est une chaîne avec une valeur nulle, que se passera-t-il alors? Astuce - votre appel ToLower échouera.
James
@james - Vous avez raison, mais qu'en est-il des cas où je sais avec certitude qu'il ne sera pas nul et que je dois juste faire le casting pour le compilateur pour me permettre d'accéder aux méthodes de cet objet?
BornToCode
1
vous pouvez certainement le faire, mais ce n'est pas exactement la meilleure pratique car vous ne voulez pas vous fier à l'appelant ou aux systèmes externes pour vous assurer que votre valeur n'est pas nulle. Si vous utilisez C # 6, vous pouvez faire (o comme chaîne)?. Baisser().
James
3
string s = o as string; // 2

Est préférable, car il évite la pénalité de performance de la double coulée.

Chris S
la source
Salut Chris, le lien qui était dans cette réponse est maintenant un 404 ... Je ne sais pas si tu as un remplaçant que tu veux mettre à sa place?
Matt
3

Il semble que les deux soient conceptuellement différents.

Casting direct

Les types n'ont pas à être strictement liés. Il se décline dans tous les types de saveurs.

  • Casting implicite / explicite personnalisé: un nouvel objet est généralement créé.
  • Type de valeur implicite: copiez sans perdre d'informations.
  • Type de valeur explicite: la copie et les informations peuvent être perdues.
  • Relation IS-A: changez le type de référence, sinon lève une exception.
  • Même type: «La diffusion est redondante».

C'est comme si l'objet allait être converti en quelque chose d'autre.

Opérateur AS

Les types ont une relation directe. Un péché:

  • Types de référence: relation IS-A Les objets sont toujours les mêmes, seules les références changent.
  • Types de valeurs: copie les types boxing et nullable.

On a l'impression que vous allez manipuler l'objet d'une manière différente.

Échantillons et IL

    class TypeA
    {
        public int value;
    }

    class TypeB
    {
        public int number;

        public static explicit operator TypeB(TypeA v)
        {
            return new TypeB() { number = v.value };
        }
    }

    class TypeC : TypeB { }
    interface IFoo { }
    class TypeD : TypeA, IFoo { }

    void Run()
    {
        TypeA customTypeA = new TypeD() { value = 10 };
        long longValue = long.MaxValue;
        int intValue = int.MaxValue;

        // Casting 
        TypeB typeB = (TypeB)customTypeA; // custom explicit casting -- IL:  call class ConsoleApp1.Program/TypeB ConsoleApp1.Program/TypeB::op_Explicit(class ConsoleApp1.Program/TypeA)
        IFoo foo = (IFoo)customTypeA; // is-a reference -- IL: castclass  ConsoleApp1.Program/IFoo

        int loseValue = (int)longValue; // explicit -- IL: conv.i4
        long dontLose = intValue; // implict -- IL: conv.i8

        // AS 
        int? wraps = intValue as int?; // nullable wrapper -- IL:  call instance void valuetype [System.Runtime]System.Nullable`1<int32>::.ctor(!0)
        object o1 = intValue as object; // box -- IL: box [System.Runtime]System.Int32
        TypeD d1 = customTypeA as TypeD; // reference conversion -- IL: isinst ConsoleApp1.Program/TypeD
        IFoo f1 = customTypeA as IFoo; // reference conversion -- IL: isinst ConsoleApp1.Program/IFoo

        //TypeC d = customTypeA as TypeC; // wouldn't compile
    }
Lucas Teixeira
la source
2

Je voudrais attirer l'attention sur les spécificités suivantes de l' opérateur as :

https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/as

Notez que l'opérateur as effectue uniquement les conversions de référence, les conversions annulables et les conversions de boxe. L'opérateur as ne peut pas effectuer d'autres conversions, telles que des conversions définies par l'utilisateur, qui devraient plutôt être effectuées à l'aide d'expressions cast.

Vadim S.
la source
0

Lorsque j'essaie d'obtenir la représentation sous forme de chaîne de tout (de tout type) qui pourrait potentiellement être nul, je préfère la ligne de code ci-dessous. Il est compact, il appelle ToString () et il gère correctement les valeurs nulles. Si o est nul, s contiendra String.Empty.

String s = String.Concat(o);
xtrem
la source
0

Puisque personne ne l'a mentionné, le plus proche de instanceOf to Java par mot-clé est le suivant:

obj.GetType().IsInstanceOfType(otherObj)
Bennett Yeo
la source
0

Utilisez la conversion directe string s = (string) o;si dans le contexte logique de votre application stringest le seul type valide. Avec cette approche, vous obtiendrez InvalidCastExceptionet implémenterez le principe de Fail-fast . Votre logique sera protégée contre la transmission du type non valide ou obtiendra NullReferenceException si l' asopérateur est utilisé .

Si la logique attend plusieurs types différents castés string s = o as string;et cochez-la nullou utilisez l' isopérateur.

De nouvelles fonctionnalités intéressantes sont apparues dans C # 7.0 pour simplifier la distribution et la vérification est une correspondance de modèle :

if(o is string s)
{
  // Use string variable s
}

or

switch (o)
{
  case int i:
     // Use int variable i
     break;
  case string s:
     // Use string variable s
     break;
 }
Dmitry
la source
0

Les deux formes suivantes de conversion de type (conversion) sont prises en charge en C #:

|

(CV

• Convertir le type statique de v en c dans l'expression donnée

• Possible uniquement si le type dynamique de v est c ou un sous-type de c

• Sinon, une InvalidCastException est levée

|

v comme C

• Variante non mortelle de (c) v

• Ainsi, convertissez le type statique de v en c dans l'expression donnée

• Renvoie null si le type dynamique de v n'est pas c, ou un sous-type de c


la source