380 votes

Casting vs à l’aide de la ' comme ' mot-clé dans le CLR

J'apprends sur les modèles de conception et parce que des que j'ai fini à l'aide de beaucoup d'interfaces. Un de mes "objectifs" est au programme pour une interface, pas de mise en œuvre.

Ce que j'ai trouvé, c'est que je fais beaucoup de moulage ou de l'objet de la conversion de type. Ce que je voudrais savoir c'est si il y a une différence entre ces deux méthodes de conversion:

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);
    }
}

Si il y a une différence, est-il une différence de coût ou comment cela affecte mon programme?

Espérons que cela a du sens. Désolé pour le mauvais exemple; c'est tout ce que je pouvais penser...

Mise à jour: Ce qui est "en général" la méthode préférée? (J'ai eu une question similaire à ce posté dans les "réponses". Je l'ai déplacé jusqu'ici, à la suggestion de Michael Haren. Aussi, je tiens à remercier tous ceux qui ont permis de mieux comprendre et de point de vue sur ma question.)

506voto

Jon Skeet Points 692016

Je ne pense pas que les réponses jusqu'à présent (à l'époque, à partir de cette réponse!) ont vraiment expliquer en quoi c'est utile.

  • Ne pas faire ceci:

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

    Ce n'est pas seulement de vérifier deux fois, mais il est peut-être la vérification de choses différentes, si randomObject est un domaine plutôt qu'une variable locale. Il est possible que le "si" à passer, mais ensuite le casting à l'échec, si un autre thread modifie la valeur de randomObject entre les deux.

  • Si randomObject vraiment devrait être une instance d' TargetType, c'est à dire si elle ne l'est pas, cela signifie qu'il ya un bug, le casting, c'est la bonne solution. Qui lève une exception immédiatement, ce qui signifie que le travail est fait sous des hypothèses erronées, et à l'exception affiche correctement le type de bug.

    // 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 pourrait être une instance d' TargetType et TargetType est un type de référence, puis utiliser le code comme ceci:

    TargetType convertedRandomObject = randomObject as TargetType;
    if (convertedRandomObject != null)
    {
        // Do stuff with convertedRandomObject
    }
    
  • Si randomObject pourrait être une instance d' TargetType et TargetType est un type valeur, alors nous ne pouvons pas utiliser as avec TargetType lui-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 lentement que est de + en fonte. Je pense que c'est plus élégant et cohérent, mais il nous continuons.)

  • Si vous n'avez pas vraiment besoin de la valeur convertie, mais vous avez juste besoin de savoir si c' est une instance de TargetType, puis l' is - opérateur est votre ami. Dans ce cas, il n'a pas d'importance si TargetType est un type de référence ou une valeur de type.

  • Il peut y avoir d'autres cas impliquant des médicaments génériques où is est utile (parce que vous ne pouvez pas savoir si T est un type référence ou pas, de sorte que vous ne peut pas utiliser), mais ils sont relativement obscure.

  • J'ai presque certainement utilisé is de la valeur type de cas, jusqu'à maintenant, n'ayant pas pensé à utiliser un type nullable et as ensemble :)


EDIT: à Noter qu'aucun des ci-dessus parle de la performance autre que la valeur de type de cas, où j'ai noté que unboxing à nullable type de valeur est en fait plus lente, mais constante.

Comme par naasking réponse, est-et de la fonte ou est-et-les aussi vite que comme-et-null-vérifier moderne, avec des équipes communes d'enquête, 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 portable, tout exécuter à propos de 60ms. Deux choses à noter:

  • Il n'y a pas de différence significative entre eux. (En fait, il existe des situations dans lesquelles la plus-null-vérifier certainement est plus lent. Le code ci-dessus fait la vérification de type facile parce que c'est pour une classe scellée; si vous êtes de vérifier qu'une interface, la balance penche légèrement en faveur des plus-null-case.)
  • Ils sont tous incroyablement rapide. Ce tout simplement ne va pas être le goulot d'étranglement dans votre code, sauf si vous avez vraiment ne vont pas faire quelque chose avec les valeurs par la suite.

Donc, nous allons vous inquiétez pas au sujet de la performance. Nous allons vous soucier de l'exactitude et de la cohérence.

Je maintiens, c'est-à-cast (ou est-et-as) sont à la fois dangereux lorsqu'ils traitent avec des variables, comme le type de la valeur qu'il désigne peut changer en raison d'un autre thread entre le test et la fonte. Qui serait assez rare de la situation - mais je préfère avoir une convention qui je peux utiliser de manière cohérente.

J'ai aussi maintenir que l'as-puis-null-vérifier donne une meilleure séparation des préoccupations. Nous avons une déclaration qui tente une conversion, puis une instruction qui utilise le résultat. L'est-et de la fonte ou est-et comme on l'effectue un test et puis une autre tentative pour convertir la valeur.

Pour le dire d'une autre façon, quelqu'un aurait-il jamais écrire:

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

C'est en quelque sorte de ce qui est-et-cast - bien que, de toute évidence, plutôt moins cher.

69voto

Patrick Desjardins Points 51478

« sous » retournera NULL si pas possible d’effectuer un cast.

casting avant lèvera une exception.

Pour la performance, levée d’une exception est généralement plus coûteux en temps.

26voto

Chris S Points 32376

Voici une autre réponse, avec quelques IL de comparaison. Considérons 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);
    	} 
    }
}

Maintenant, regardez le IL chaque méthode produit. Même si l'op codes ne signifient rien pour vous, vous pouvez voir une différence majeure: isinst est appelée, suivi par castclass dans le DirectCast méthode. Donc, de deux appels au lieu d'un gros.

.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 isinst mot-clé par rapport à la castclass

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

  • Dans une comparaison directe, isinst est plus rapide que castclass (bien que légèrement)
  • Lorsqu'd'avoir à effectuer des contrôles pour s'assurer de la conversion a réussi, isinst était significativement plus rapide que castclass
  • Une combinaison de isinst et castclass ne doit pas être utilisé comme cela a été beaucoup plus lent que le moyen le plus rapide "de sécurité", conversion (plus de 12% plus lent)

Personnellement, j'ai toujours l'utiliser Comme, parce que c'est facile à lire et est recommandé par le .NET de l'équipe de développement ou de Jeffrey Richter, de toute façon)

18voto

Patrik Hägne Points 7735

L'un des plus subtiles différences entre les deux est que le "comme" mot-clé ne peut pas être utilisé pour la coulée lorsqu'un opérateur de cast est en cause:

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;
    }
}

Ce ne compile pas (même si je pense que dans les versions précédentes) sur la dernière ligne depuis le "comme" mots-clés de ne pas prendre des opérateurs de transtypage en compte. La ligne string cast = (string)f; fonctionne parfaitement bien.

12voto

Anton Gogolev Points 59794

comme jamais déclenche une exception si elle ne peut pas effectuer la conversion de retourner la valeur null à la place (comme fonctionne sur les types de référence uniquement). Donc, en utilisant comme est essentiellement équivalent à

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

C-style de moulages, d'autre part, de lancer une exception si aucune conversion n'est possible.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X