Casting vs utilisation du mot clé "as" dans le CLR

387

Lors de la programmation des interfaces, j'ai constaté que je fais beaucoup de conversion ou de conversion de type d'objet.

Y a-t-il une différence entre ces deux méthodes de conversion? Si oui, y a-t-il une différence de coût ou comment cela affecte-t-il mon programme?

public interface IMyInterface
{
    void AMethod();
}

public class MyClass : IMyInterface
{
    public void AMethod()
    {
       //Do work
    }

    // Other helper methods....
}

public class Implementation
{
    IMyInterface _MyObj;
    MyClass _myCls1;
    MyClass _myCls2;

    public Implementation()
    {
        _MyObj = new MyClass();

        // What is the difference here:
        _myCls1 = (MyClass)_MyObj;
        _myCls2 = (_MyObj as MyClass);
    }
}

De plus, quelle est «en général» la méthode préférée?

Frank V
la source
Pourriez-vous ajouter un petit exemple de la raison pour laquelle vous utilisez les transtypages en premier lieu à la question, ou peut-être en commencer un nouveau? Je suis un peu intéressé par la raison pour laquelle vous auriez besoin du casting pour les tests unitaires uniquement. Je pense que cela sort du cadre de cette question.
Erik van Brakel le
2
Je peux probablement changer mon test unitaire pour éviter ce besoin. Fondamentalement, cela se résume au fait que j'ai une propriété sur mon objet concret qui n'est pas dans l'interface. Je dois définir cette propriété, mais dans la réalité, cette propriété aurait été définie par d'autres moyens. Est-ce que ça répond à votre question?
Frank V
Patrik Hagne souligne astucieusement ci - dessous, il EST une différence.
Neil

Réponses:

520

La réponse sous la ligne a été écrite en 2008.

C # 7 a introduit la correspondance de motifs, qui a largement remplacé l' asopérateur, comme vous pouvez maintenant l'écrire:

if (randomObject is TargetType tt)
{
    // Use tt here
}

Notez que ttc'est toujours dans la portée après cela, mais pas définitivement attribué. (Il est définitivement attribué dans le ifcorps.) C'est légèrement ennuyeux dans certains cas, donc si vous vous souciez vraiment d'introduire le plus petit nombre de variables possible dans chaque étendue, vous voudrez peut-être toujours utiliser issuivi d'un cast.


Je ne pense pas qu'aucune des réponses à ce jour (au moment de commencer cette réponse!) Ait vraiment expliqué où cela valait la peine d'être utilisé.

  • Ne faites pas ça:

    // Bad code - checks type twice for no reason
    if (randomObject is TargetType)
    {
        TargetType foo = (TargetType) randomObject;
        // Do something with foo
    }

    Non seulement cette vérification est effectuée deux fois, mais elle peut également vérifier différentes choses, s'il randomObjects'agit d'un champ plutôt que d'une variable locale. Il est possible que le «si» passe mais que la conversion échoue, si un autre thread change la valeur randomObjectentre les deux.

  • Si randomObjectvraiment devrait être une instance de TargetType, c'est-à-dire si ce n'est pas le cas, cela signifie qu'il y a un bug, alors le casting est la bonne solution. Cela lève immédiatement une exception, ce qui signifie qu'aucun travail supplémentaire n'est effectué sous des hypothèses incorrectes, et l'exception affiche correctement le type de bogue.

    // This will throw an exception if randomObject is non-null and
    // refers to an object of an incompatible type. The cast is
    // the best code if that's the behaviour you want.
    TargetType convertedRandomObject = (TargetType) randomObject;
  • Si randomObject peut être une instance de TargetTypeet TargetTypeest un type de référence, utilisez un code comme celui-ci:

    TargetType convertedRandomObject = randomObject as TargetType;
    if (convertedRandomObject != null)
    {
        // Do stuff with convertedRandomObject
    }
  • Si randomObject peut être une instance de TargetTypeet TargetTypeest un type valeur, nous ne pouvons pas l'utiliser asavec TargetTypelui-même, mais nous pouvons utiliser un type nullable:

    TargetType? convertedRandomObject = randomObject as TargetType?;
    if (convertedRandomObject != null)
    {
        // Do stuff with convertedRandomObject.Value
    }

    (Remarque: actuellement, c'est en fait plus lent que + cast . Je pense que c'est plus élégant et cohérent, mais c'est parti.)

  • Si vous n'avez vraiment pas besoin de la valeur convertie, mais que vous avez juste besoin de savoir s'il s'agit d' une instance de TargetType, alors l' isopérateur est votre ami. Dans ce cas, peu importe que TargetType soit un type de référence ou un type de valeur.

  • Il peut y avoir d'autres cas impliquant des génériques où cela isest utile (parce que vous ne savez pas si T est un type de référence ou non, vous ne pouvez donc pas l'utiliser comme) mais ils sont relativement obscurs.

  • Je l'ai presque certainement utilisé ispour le cas de type valeur avant maintenant, sans avoir pensé à utiliser un type nullable et asensemble :)


EDIT: Notez qu'aucun des éléments ci-dessus ne parle de performances, à l'exception du cas du type de valeur, où j'ai remarqué que la décompression vers un type de valeur nullable est en fait plus lente - mais cohérente.

Selon la réponse de naasking, is-and-cast ou is-and-as sont à la fois aussi rapides que la vérification as-and-null avec les JIT modernes, comme le montre le code ci-dessous:

using System;
using System.Diagnostics;
using System.Linq;

class Test
{
    const int Size = 30000000;

    static void Main()
    {
        object[] values = new object[Size];
        for (int i = 0; i < Size - 2; i += 3)
        {
            values[i] = null;
            values[i + 1] = "x";
            values[i + 2] = new object();
        }
        FindLengthWithIsAndCast(values);
        FindLengthWithIsAndAs(values);
        FindLengthWithAsAndNullCheck(values);
    }

    static void FindLengthWithIsAndCast(object[] values)        
    {
        Stopwatch sw = Stopwatch.StartNew();
        int len = 0;
        foreach (object o in values)
        {
            if (o is string)
            {
                string a = (string) o;
                len += a.Length;
            }
        }
        sw.Stop();
        Console.WriteLine("Is and Cast: {0} : {1}", len,
                          (long)sw.ElapsedMilliseconds);
    }

    static void FindLengthWithIsAndAs(object[] values)        
    {
        Stopwatch sw = Stopwatch.StartNew();
        int len = 0;
        foreach (object o in values)
        {
            if (o is string)
            {
                string a = o as string;
                len += a.Length;
            }
        }
        sw.Stop();
        Console.WriteLine("Is and As: {0} : {1}", len,
                          (long)sw.ElapsedMilliseconds);
    }

    static void FindLengthWithAsAndNullCheck(object[] values)        
    {
        Stopwatch sw = Stopwatch.StartNew();
        int len = 0;
        foreach (object o in values)
        {
            string a = o as string;
            if (a != null)
            {
                len += a.Length;
            }
        }
        sw.Stop();
        Console.WriteLine("As and null check: {0} : {1}", len,
                          (long)sw.ElapsedMilliseconds);
    }
}

Sur mon ordinateur portable, ils s'exécutent tous en 60 ms environ. Deux choses à noter:

  • Il n'y a pas de différence significative entre eux. (En fait, il existe des situations dans lesquelles la vérification as-plus-null est certainement plus lente. Le code ci-dessus facilite en fait la vérification de type car il s'agit d'une classe scellée; si vous recherchez une interface, l'équilibre bascule légèrement en faveur de as-plus-null-check.)
  • Ils sont tous incroyablement rapides. Ce ne sera tout simplement pas le goulot d'étranglement dans votre code, sauf si vous n'allez vraiment rien faire avec les valeurs par la suite.

Alors ne nous inquiétons pas des performances. Soucions-nous de l'exactitude et de la cohérence.

Je maintiens que is-and-cast (ou is-and-as) ne sont pas sûrs lorsqu'ils traitent avec des variables, car le type de la valeur à laquelle il se réfère peut changer en raison d'un autre thread entre le test et le cast. Ce serait une situation assez rare - mais je préfère avoir une convention que je peux utiliser de manière cohérente.

Je maintiens également que la vérification as-then-null donne une meilleure séparation des préoccupations. Nous avons une instruction qui tente une conversion, puis une instruction qui utilise le résultat. Le is-and-cast ou is-and-as effectue un test, puis une autre tentative de conversion de la valeur.

Autrement dit, quelqu'un pourrait-il jamais écrire:

int value;
if (int.TryParse(text, out value))
{
    value = int.Parse(text);
    // Use value
}

C'est en quelque sorte ce que fait-et-cast - bien que de manière évidemment moins chère.

Jon Skeet
la source
7
Voici le coût de is / as / casting en termes d'IL: atalasoft.com/cs/blogs/stevehawley/archive/2009/01/30/…
plinth
3
Dans le cas, si targetObject pourrait être de type cible, pourquoi l'utilisation de "is" et d'une combinaison de cast est-elle considérée comme une mauvaise pratique? Je veux dire, il génère un code plus lent, mais dans ce cas, les intentions sont plus claires que le cast AS, comme "Faire quelque chose si targetObject est targetType", au lieu de "Faire quelque chose si targetObject est nul", De plus, la clause AS créera une variable inutile hors champ IF.
Valera Kolupaev
2
@Valera: Bons points, bien que je suggère que le test as / null soit suffisamment idiomatique pour que l'intention soit claire pour presque tous les développeurs C #. Personnellement, je n'aime pas la duplication impliquée dans le casting is +. J'aimerais en fait une sorte de construction "comme si" qui fait les deux actions en une. Ils vont si souvent ensemble ...
Jon Skeet
2
@ Jon Skeet: désolé pour mon retard.Est-ce que Cast: 2135, est et comme: 2145, As Et null check: 1961, spécifications: OS: Windows Seven, CPU: i5-520M, 4 Go de RAM DDR3 1033, référence sur la baie de 128 millions d'articles.
Behrooz
2
Avec C # 7, vous pouvez faire: if (randomObject is TargetType convertedRandomObject){ // Do stuff with convertedRandomObject.Value}ou utiliser switch/ case voir les documents
WerWet
72

"as" renverra NULL s'il n'est pas possible de transtyper.

lancer avant lèvera une exception.

Pour les performances, lever une exception est généralement plus coûteux en temps.

Patrick Desjardins
la source
3
Exception levée est plus coûteuse, mais si vous savez que l'objet peut être jeté correctement, comme exige plus de temps en raison de la vérification de la sécurité (voir la réponse de Anton). Cependant, le coût du contrôle de sécurité est, je crois, assez faible.
17
Le coût de la levée potentielle d'une exception est un facteur à considérer, mais c'est souvent la bonne conception.
Jeffrey L Whitledge
@panesofglass - Pour les types de référence, la compatibilité de conversion sera toujours vérifiée au moment de l'exécution à la fois pour as et cast, afin que ce facteur ne fasse pas de distinction entre les deux options. (Si ce n'était pas le cas, le casting ne pourrait pas soulever une exception.)
Jeffrey L Whitledge
4
@Frank - Si vous devez utiliser une collection pré-générique, par exemple, et qu'une méthode de votre API nécessite une liste d'employés, et qu'un joker transmet à la place une liste de produits, une exception de conversion non valide peut être appropriée pour signaler la violation des exigences d'interface.
Jeffrey L Whitledge
27

Voici une autre réponse, avec une comparaison IL. Considérez la classe:

public class MyClass
{
    public static void Main()
    {
        // Call the 2 methods
    }

    public void DirectCast(Object obj)
    {
        if ( obj is MyClass)
        { 
            MyClass myclass = (MyClass) obj; 
            Console.WriteLine(obj);
        } 
    } 


    public void UsesAs(object obj) 
    { 
        MyClass myclass = obj as MyClass; 
        if (myclass != null) 
        { 
            Console.WriteLine(obj);
        } 
    }
}

Regardez maintenant l'IL produit par chaque méthode. Même si les codes op ne signifient rien pour vous, vous pouvez voir une différence majeure - isinst est appelé suivi de castclass dans la méthode DirectCast. Donc, deux appels au lieu d'un essentiellement.

.method public hidebysig instance void  DirectCast(object obj) cil managed
{
  // Code size       22 (0x16)
  .maxstack  8
  IL_0000:  ldarg.1
  IL_0001:  isinst     MyClass
  IL_0006:  brfalse.s  IL_0015
  IL_0008:  ldarg.1
  IL_0009:  castclass  MyClass
  IL_000e:  pop
  IL_000f:  ldarg.1
  IL_0010:  call       void [mscorlib]System.Console::WriteLine(object)
  IL_0015:  ret
} // end of method MyClass::DirectCast

.method public hidebysig instance void  UsesAs(object obj) cil managed
{
  // Code size       17 (0x11)
  .maxstack  1
  .locals init (class MyClass V_0)
  IL_0000:  ldarg.1
  IL_0001:  isinst     MyClass
  IL_0006:  stloc.0
  IL_0007:  ldloc.0
  IL_0008:  brfalse.s  IL_0010
  IL_000a:  ldarg.1
  IL_000b:  call       void [mscorlib]System.Console::WriteLine(object)
  IL_0010:  ret
} // end of method MyClass::UsesAs

Le mot-clé isinst contre la classe de distribution

Ce billet de blog a une comparaison décente entre les deux façons de le faire. Son résumé est:

  • En comparaison directe, isinst est plus rapide que castclass (bien que légèrement)
  • Lorsque vous devez effectuer des vérifications pour vous assurer que la conversion a réussi, isinst était beaucoup plus rapide que castclass
  • Une combinaison de isinst et castclass ne doit pas être utilisée car elle était beaucoup plus lente que la conversion "sûre" la plus rapide (plus de 12% plus lente)

Personnellement, j'utilise toujours As, car il est facile à lire et recommandé par l'équipe de développement .NET (ou Jeffrey Richter de toute façon)

Chris S
la source
Je cherchais une explication claire pour le casting vs as, cette réponse le rend beaucoup plus clair car il implique une explication étape par étape du langage intermédiaire commun. Merci!
Morse
18

L'une des différences les plus subtiles entre les deux est que le mot clé "as" ne peut pas être utilisé pour le cast lorsqu'un opérateur de cast est impliqué:

public class Foo
{
    public string Value;

    public static explicit operator string(Foo f)
    {
        return f.Value;
    }

}

public class Example
{
    public void Convert()
    {
        var f = new Foo();
        f.Value = "abc";

        string cast = (string)f;
        string tryCast = f as string;
    }
}

Cela ne se compilera pas (bien que je pense que ce soit le cas dans les versions précédentes) sur la dernière ligne car les mots clés "as" ne prennent pas en compte les opérateurs de cast. La ligne string cast = (string)f;fonctionne très bien cependant.

Patrik Hägne
la source
12

as ne lève jamais d'exception s'il ne peut pas effectuer la conversion en retournant null à la place ( comme pour les types de référence uniquement). Donc, utiliser as est fondamentalement équivalent à

_myCls2 = _myObj is MyClass ? (MyClass)_myObj : null;

Les conversions de style C, en revanche, lèvent une exception lorsqu'aucune conversion n'est possible.

Anton Gogolev
la source
4
Équivalent, oui, mais pas le même. Cela génère bien plus de code que.
socle le
10

Pas vraiment une réponse à votre question, mais ce que je pense est un point connexe important.

Si vous programmez sur une interface, vous ne devriez pas avoir besoin de caster. Espérons que ces lancers soient très rares. Sinon, vous devrez probablement repenser certaines de vos interfaces.

crapaud
la source
Jusqu'à présent, le casting a été principalement nécessaire pour mes tests unitaires, mais merci de l'avoir soulevé. Je garderai cela à l'esprit pendant que je travaillerai là-dessus.
Frank V
D'accord avec le crapaud, je suis également curieux de savoir pourquoi l'aspect test unitaire est pertinent pour le casting pour vous @Frank V.Lorsqu'il y a un besoin de casting, il y a souvent un besoin de refonte ou de refactorisation car cela suggère que vous essayez à chausse-pied différents problèmes où ils devraient être gérés différemment.
Le sénateur
@TheSenator Cette question a bien plus de 3 ans, donc je ne m'en souviens pas vraiment. Mais j'utilisais probablement les interfaces de manière agressive même lors des tests unitaires. Peut-être parce que j'utilisais le modèle d'usine et que je n'avais pas accès à un constructeur public sur les objets cibles à tester.
Frank V
9

Veuillez ignorer les conseils de Jon Skeet, concernant: éviter le modèle de test et de distribution, c'est-à-dire:

if (randomObject is TargetType)
{
    TargetType foo = randomObject as TargetType;
    // Do something with foo
}

L'idée que cela coûte plus cher qu'un casting et un test nul est un MYTHE :

TargetType convertedRandomObject = randomObject as TargetType;
if (convertedRandomObject != null)
{
    // Do stuff with convertedRandomObject
}

C'est une micro-optimisation qui ne fonctionne pas. J'ai effectué de vrais tests , et test-and-cast est en fait plus rapide que cast-and-null-comparaison, et c'est aussi plus sûr parce que vous n'avez pas la possibilité d'avoir une référence nulle dans la portée en dehors du si le cast échouer.

Si vous voulez une raison pour laquelle le test et la diffusion sont plus rapides, ou du moins pas plus lents, il y a une raison simple et complexe.

Simple: même les compilateurs naïfs fusionneront deux opérations similaires, comme test-and-cast, en un seul test et une seule branche. cast-and-null-test peut forcer deux tests et une branche, une pour le test de type et la conversion en null en cas d'échec, une pour la vérification null elle-même. À tout le moins, ils seront tous deux optimisés en un seul test et une seule branche, de sorte que test-and-cast ne serait ni plus lent ni plus rapide que cast-and-null-test.

Complexe: pourquoi le test et le cast sont plus rapides: cast-and-null-test introduit une autre variable dans la portée externe que le compilateur doit suivre pour la vivacité, et il peut ne pas être en mesure d'optimiser cette variable en fonction de la complexité de votre contrôle - le flux est. Inversement, test-and-cast n'introduit une nouvelle variable que dans une portée délimitée afin que le compilateur sache que la variable est morte après la fin de la portée et peut ainsi optimiser l'allocation des registres.

Alors s'il vous plait, VEUILLEZ laisser ce DIE "conseil cast-and-null-test est meilleur que test-and-cast". S'IL VOUS PLAÎT. test-and-cast est à la fois plus sûr et plus rapide.

naasking
la source
7
@naasking: Si vous testez deux fois (selon votre premier extrait), il est possible que le type change entre les deux tests, s'il s'agit d'un champ ou d'un refparamètre. Il est sûr pour les variables locales, mais pas pour les champs. Je serais intéressé à exécuter vos benchmarks, mais le code que vous avez donné dans votre article de blog n'est pas complet. Je suis d'accord pour ne pas micro-optimiser, mais je ne pense pas que l'utilisation de la valeur deux fois soit plus lisible ou élégante que l'utilisation de "as" et d'un test de nullité. (J'utiliserais certainement un casting droit plutôt que "as" après un is, btw.)
Jon Skeet
5
Je ne vois pas non plus pourquoi c'est plus sûr. J'ai montré pourquoi c'est moins sûr, en fait. Bien sûr, vous vous retrouvez avec une variable dans la portée qui peut être nulle, mais à moins que vous ne commenciez à l'utiliser en dehors de la portée du bloc "if" suivant, tout va bien. Le problème de sécurité que j'ai soulevé (autour des champs qui changent leur valeur) est un véritable problème avec le code affiché - votre problème de sécurité nécessite que les développeurs soient laxistes dans un autre code.
Jon Skeet
1
+1 pour avoir souligné que / cast ou as / cast n'est pas plus lent en réalité, faites attention. Ayant moi-même effectué un test complet, je peux confirmer que cela ne fait aucune différence pour autant que je puisse voir - et franchement, vous pouvez exécuter un nombre ahurissant de lancers en très peu de temps. Va mettre à jour ma réponse avec le code complet.
Jon Skeet
1
En effet, si la liaison n'est pas locale, il y a un risque de bug TOCTTOU (time-of-check-to-time-of-use), donc bon point là. Quant à savoir pourquoi c'est plus sûr, je travaille avec beaucoup de développeurs juniors qui aiment réutiliser les locaux pour une raison quelconque. cast-and-null est donc un danger très réel dans mon expérience, et je n'ai jamais rencontré de situation TOCTTOU car je ne conçois pas mon code de cette façon. Quant à la vitesse de test d'exécution, elle est encore plus rapide que la répartition virtuelle [1]! Re: code, je vais voir si je peux trouver la source du test de casting. [1] Higherlogics.blogspot.com/2008/10/…
naasking
1
@naasking: Je n'ai jamais rencontré de problème de réutilisation local - mais je dirais qu'il est plus facile de repérer dans la revue de code que le bogue TOCTTOU plus subtil. Il convient également de souligner que je viens de réexécuter mon propre test de référence pour vérifier les interfaces au lieu d'une classe scellée, ce qui fait pencher la performance en faveur de as-then-null-check ... mais comme je l'ai dit, les performances ne sont pas 't pourquoi je choisirais une approche particulière ici.
Jon Skeet
4

Si la conversion échoue, le mot clé 'as' ne lève pas d'exception; il définit la variable sur null (ou sur sa valeur par défaut pour les types de valeur) à la place.

Le Schtroumpf
la source
3
Aucune valeur par défaut pour les types de valeur. As ne peut pas être utilisé pour la conversion de types de valeur.
Patrik Hägne
2
Le mot clé "as" ne fonctionne pas sur les types de valeur en fait, il est donc toujours défini sur null.
Erik van Brakel
4

Ce n'est pas une réponse à la question mais un commentaire sur l'exemple de code de la question:

Habituellement, vous ne devriez pas avoir à convertir un objet, par exemple IMyInterface, en MyClass. La grande chose au sujet des interfaces est que si vous prenez un objet en entrée qui implémente une interface, vous n'avez pas à vous soucier du type d'objet que vous obtenez.

Si vous convertissez IMyInterface en MyClass, vous supposez déjà que vous obtenez un objet de type MyClass et cela n'a aucun sens d'utiliser IMyInterface, car si vous alimentez votre code avec d'autres classes qui implémentent IMyInterface, cela casserait votre code ...

Maintenant, mon conseil: si vos interfaces sont bien conçues, vous pouvez éviter beaucoup de transtypage.

f3lix
la source
3

L' asopérateur ne peut être utilisé que sur les types de référence, il ne peut pas être surchargé et il reviendra nullsi l'opération échoue. Il ne lèvera jamais d'exception.

Le casting peut être utilisé sur tous les types compatibles, il peut être surchargé et il lèvera une exception si l'opération échoue.

Le choix de l'utilisation dépend des circonstances. Il s'agit principalement de savoir si vous souhaitez lever une exception en cas d'échec de la conversion.

Jeffrey L Whitledge
la source
1
'as' peut également être utilisé sur les types de valeurs nullables, ce qui fournit un modèle intéressant. Voir ma réponse pour le code.
Jon Skeet
1

Ma réponse ne concerne que la vitesse dans les cas où nous ne vérifions pas le type et nous ne vérifions pas les valeurs nulles après le casting. J'ai ajouté deux tests supplémentaires au code de Jon Skeet:

using System;
using System.Diagnostics;

class Test
{
    const int Size = 30000000;

    static void Main()
    {
        object[] values = new object[Size];

        for (int i = 0; i < Size; i++)
        {
            values[i] = "x";
        }
        FindLengthWithIsAndCast(values);
        FindLengthWithIsAndAs(values);
        FindLengthWithAsAndNullCheck(values);

        FindLengthWithCast(values);
        FindLengthWithAs(values);

        Console.ReadLine();
    }

    static void FindLengthWithIsAndCast(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int len = 0;
        foreach (object o in values)
        {
            if (o is string)
            {
                string a = (string)o;
                len += a.Length;
            }
        }
        sw.Stop();
        Console.WriteLine("Is and Cast: {0} : {1}", len,
                          (long)sw.ElapsedMilliseconds);
    }

    static void FindLengthWithIsAndAs(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int len = 0;
        foreach (object o in values)
        {
            if (o is string)
            {
                string a = o as string;
                len += a.Length;
            }
        }
        sw.Stop();
        Console.WriteLine("Is and As: {0} : {1}", len,
                          (long)sw.ElapsedMilliseconds);
    }

    static void FindLengthWithAsAndNullCheck(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int len = 0;
        foreach (object o in values)
        {
            string a = o as string;
            if (a != null)
            {
                len += a.Length;
            }
        }
        sw.Stop();
        Console.WriteLine("As and null check: {0} : {1}", len,
                          (long)sw.ElapsedMilliseconds);
    }
    static void FindLengthWithCast(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int len = 0;
        foreach (object o in values)
        {
            string a = (string)o;
            len += a.Length;
        }
        sw.Stop();
        Console.WriteLine("Cast: {0} : {1}", len,
                          (long)sw.ElapsedMilliseconds);
    }

    static void FindLengthWithAs(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int len = 0;
        foreach (object o in values)
        {
            string a = o as string;
            len += a.Length;
        }
        sw.Stop();
        Console.WriteLine("As: {0} : {1}", len,
                          (long)sw.ElapsedMilliseconds);
    }
}

Résultat:

Is and Cast: 30000000 : 88
Is and As: 30000000 : 93
As and null check: 30000000 : 56
Cast: 30000000 : 66
As: 30000000 : 46

N'essayez pas de vous concentrer sur la vitesse (comme je l'ai fait) car tout cela est très très rapide.

CoperNick
la source
De même, lors de mes tests, j'ai trouvé que la asconversion (sans vérification d'erreur) fonctionnait environ 1 à 3% plus rapidement que la conversion (environ 540 ms contre 550 ms sur 100 millions d'itérations). Aucun ne fera ou ne cassera votre candidature.
palswim
1

En plus de tout ce qui a déjà été exposé ici, je suis juste tombé sur une différence pratique, je pense, à noter, entre un casting explicite

var x = (T) ...

par rapport à l'utilisation de l' asopérateur.

Voici l'exemple:

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine(GenericCaster<string>(12345));
        Console.WriteLine(GenericCaster<object>(new { a = 100, b = "string" }) ?? "null");
        Console.WriteLine(GenericCaster<double>(20.4));

        //prints:
        //12345
        //null
        //20.4

        Console.WriteLine(GenericCaster2<string>(12345));
        Console.WriteLine(GenericCaster2<object>(new { a = 100, b = "string" }) ?? "null");

        //will not compile -> 20.4 does not comply due to the type constraint "T : class"
        //Console.WriteLine(GenericCaster2<double>(20.4));
    }

    static T GenericCaster<T>(object value, T defaultValue = default(T))
    {
        T castedValue;
        try
        {
            castedValue = (T) Convert.ChangeType(value, typeof(T));
        }
        catch (Exception)
        {
            castedValue = defaultValue;
        }

        return castedValue;
    }

    static T GenericCaster2<T>(object value, T defaultValue = default(T)) where T : class
    {
        T castedValue;
        try
        {
            castedValue = Convert.ChangeType(value, typeof(T)) as T;
        }
        catch (Exception)
        {
            castedValue = defaultValue;
        }

        return castedValue;
    }
}

Conclusion : GenericCaster2 ne fonctionnera pas avec les types de structure. GenericCaster le fera.

Veverke
la source
1

Si vous utilisez les PIA Office ciblant le .NET Framework 4.X, vous devez utiliser le mot clé as , sinon il ne sera pas compilé.

Microsoft.Office.Interop.Outlook.Application o = new Microsoft.Office.Interop.Outlook.Application();
Microsoft.Office.Interop.Outlook.MailItem m = o.CreateItem(Microsoft.Office.Interop.Outlook.OlItemType.olMailItem) as Microsoft.Office.Interop.Outlook.MailItem;

La diffusion est OK lorsque vous ciblez .NET 2.0:

Microsoft.Office.Interop.Outlook.MailItem m = (Microsoft.Office.Interop.Outlook.MailItem)o.CreateItem(Microsoft.Office.Interop.Outlook.OlItemType.olMailItem);

Lors du ciblage de .NET 4.X, les erreurs sont les suivantes:

erreur CS0656: Le compilateur manquant a requis le membre «Microsoft.CSharp.RuntimeBinder.Binder.Convert»

erreur CS0656: Le compilateur manquant a requis le membre «Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo.Create»

Olivier MATROT
la source
0

Le asmot-clé fonctionne de la même manière qu'une conversion explicite entre les types de référence compatibles, à la différence près qu'il ne déclenche pas d'exception si la conversion échoue. Au contraire, il donne une valeur nulle dans la variable cible. Étant donné que les exceptions sont très coûteuses en termes de performances, elles sont considérées comme une bien meilleure méthode de casting.

Cerebrus
la source
Pas le même, car l'un appelle CastClass et l'autre appelle IsInst en code IL.
Jenix
0

Ce que vous choisissez dépend fortement de ce qui est requis. Je préfère le casting explicite

IMyInterface = (IMyInterface)someobj;

parce que si l'objet doit être de type IMyInterface et qu'il ne l'est pas - c'est définitivement un problème. Il est préférable d'obtenir l'erreur le plus tôt possible car l'erreur exacte sera corrigée au lieu de corriger son effet secondaire.

Mais si vous traitez des méthodes qui acceptent objectcomme paramètre, vous devez vérifier son type exact avant d'exécuter un code. Dans ce cas, il asserait utile que vous puissiez éviter InvalidCastException.

Oleg
la source
0

Cela dépend, voulez-vous vérifier null après avoir utilisé "as" ou préféreriez-vous que votre application lève une exception?

Ma règle d'or est que si je m'attends toujours à ce que la variable soit du type auquel je m'attends au moment où je veux, j'utilise un transtypage. S'il est possible que la variable ne soit pas convertie en ce que je veux et que je suis prêt à gérer les valeurs nulles à l'aide de as, j'utiliserai as.

Darryl Braaten
la source
0

Le problème de l'OP est limité à une situation de casting spécifique. Le titre couvre bien plus de situations.
Voici un aperçu de toutes les situations de casting pertinentes auxquelles je peux actuellement penser:

private class CBase
{
}

private class CInherited : CBase
{
}

private enum EnumTest
{
  zero,
  one,
  two
}

private static void Main (string[] args)
{
  //########## classes ##########
  // object creation, implicit cast to object
  object oBase = new CBase ();
  object oInherited = new CInherited ();

  CBase oBase2 = null;
  CInherited oInherited2 = null;
  bool bCanCast = false;

  // explicit cast using "()"
  oBase2 = (CBase)oBase;    // works
  oBase2 = (CBase)oInherited;    // works
  //oInherited2 = (CInherited)oBase;   System.InvalidCastException
  oInherited2 = (CInherited)oInherited;    // works

  // explicit cast using "as"
  oBase2 = oBase as CBase;
  oBase2 = oInherited as CBase;
  oInherited2 = oBase as CInherited;  // returns null, equals C++/CLI "dynamic_cast"
  oInherited2 = oInherited as CInherited;

  // testing with Type.IsAssignableFrom(), results (of course) equal the results of the cast operations
  bCanCast = typeof (CBase).IsAssignableFrom (oBase.GetType ());    // true
  bCanCast = typeof (CBase).IsAssignableFrom (oInherited.GetType ());    // true
  bCanCast = typeof (CInherited).IsAssignableFrom (oBase.GetType ());    // false
  bCanCast = typeof (CInherited).IsAssignableFrom (oInherited.GetType ());    // true

  //########## value types ##########
  int iValue = 2;
  double dValue = 1.1;
  EnumTest enValue = EnumTest.two;

  // implicit cast, explicit cast using "()"
  int iValue2 = iValue;   // no cast
  double dValue2 = iValue;  // implicit conversion
  EnumTest enValue2 = (EnumTest)iValue;  // conversion by explicit cast. underlying type of EnumTest is int, but explicit cast needed (error CS0266: Cannot implicitly convert type 'int' to 'test01.Program.EnumTest')

  iValue2 = (int)dValue;   // conversion by explicit cast. implicit cast not possible (error CS0266: Cannot implicitly convert type 'double' to 'int')
  dValue2 = dValue;
  enValue2 = (EnumTest)dValue;  // underlying type is int, so "1.1" beomces "1" and then "one"

  iValue2 = (int)enValue;
  dValue2 = (double)enValue;
  enValue2 = enValue;   // no cast

  // explicit cast using "as"
  // iValue2 = iValue as int;   error CS0077: The as operator must be used with a reference type or nullable type
}
Tobias Knauss
la source