web-dev-qa-db-fra.com

Casting vs utilisant le mot clé 'as' dans le CLR

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

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

Aussi, quelle est "en général" la méthode préférée?

372
Frank V

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

C # 7 a introduit la correspondance de modèle, qui a largement remplacé l'opérateur as, comme vous pouvez maintenant l'écrire:

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

Notez que tt est toujours dans la portée après cela, mais n'est pas définitivement attribué. (Il est définitivement attribué dans le corps if.) C'est légèrement gênant dans certains cas, donc si vous tenez vraiment à introduire le plus petit nombre de variables possibles dans chaque portée, vous pouvez toujours utiliser is suivi d’un transtypage.


Je pense qu'aucune des réponses à ce jour (au moment de commencer cette réponse!) N'a vraiment expliqué où il vaut la peine de l'utiliser.

  • Ne fais 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 il peut également vérifier différentes choses si randomObject est un champ plutôt qu'une variable locale. Il est possible que le "if" réussisse mais la distribution échoue si un autre thread modifie la valeur de randomObject entre les deux.

  • Si randomObject vraiment devrait être une instance de TargetType, c'est-à-dire que si ce n'est pas le cas, cela signifie qu'il y a un bogue, 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 que l’exception indique 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 pourrait être une instance de TargetType et TargetType est un type de référence, utilisez code comme ceci:

    TargetType convertedRandomObject = randomObject as TargetType;
    if (convertedRandomObject != null)
    {
        // Do stuff with convertedRandomObject
    }
    
  • Si randomObject pourrait être une instance de TargetType et TargetType est un type de valeur, alors nous ne peut 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 réalité, plus lent que + cast . Je pense que c'est plus élégant et cohérent, mais on y va.)

  • Si vous n'avez vraiment pas besoin de la valeur convertie, mais que vous ayez juste besoin de savoir si elle est une instance de TargetType, alors le is l'opé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ù is est utile (parce que vous ne savez peut-être pas si T est un type de référence, vous ne pouvez donc pas utiliser as), mais ils sont relativement obscurs.

  • J'ai presque certainement utilisé is pour le cas du type valeur, sans avoir pensé à utiliser un type nullable et as ensemble :)


EDIT: Notez que rien de ce qui précède ne parle de performance, mis à part le cas du type de valeur, où j’ai noté que le déballage en un type de valeur nullable est en réalité plus lent - mais cohérent.

Selon la réponse de naasking, les opérations is-and-cast ou is-and-as sont toutes deux aussi rapides que la vérification as-et-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 le contrôle as-plus-null-check est définitivement plus lent . Le code ci-dessus facilite en fait le contrôle de type car il est pour une classe scellée; si vous recherchez une interface, la balance penche légèrement en faveur de la vérification as-plus-null.)
  • Ils sont tous insanement rapidement. Ceci simplement ne sera pas le goulot d’étranglement de votre code à moins que vous n’alliez vraiment pas faire quoi que ce soit avec les valeurs après.

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

Je maintiens que les variables is-and-cast (ou is-and-as) ne sont pas sûres lorsqu'il s'agit de variables, car le type de la valeur à laquelle elle fait référence peut changer en raison d'un autre lien entre le test et la conversion. Ce serait une situation assez rare - mais je préférerais une convention que je puisse utiliser de manière cohérente.

Je soutiens également que le contrôle "à l'état nul" permet de mieux séparer les problèmes. Nous avons une déclaration qui tente une conversion, puis une déclaration qui utilise le résultat. L'is-and-cast ou is-and-as effectue un test et puis une autre tentative de conversion de la valeur.

En d'autres termes, quelqu'un aurait-il jamais écrit :

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

C’est un peu ce que font les acteurs du marché - bien qu’à l’évidence de façon moins onéreuse.

499
Jon Skeet

"as" retournera NULL s'il n'est pas possible de le lancer.

casting avant lèvera une exception.

Pour la performance, le fait de lever une exception est généralement plus coûteux en temps.

69
Patrick Desjardins

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

Maintenant, regardez l'IL que chaque méthode produit. 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 par rapport à la classe cast

Cet article 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)
  • Isinst était nettement plus rapide que castclass lorsqu'il fallait effectuer des vérifications pour s'assurer que la conversion avait abouti.
  • 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% moins rapide)

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)

26
Chris S

L'une des différences les plus subtiles entre les deux est que le mot clé "en tant que" ne peut pas être utilisé pour la diffusion lorsqu'un opérateur de diffusion 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 compilera pas (bien que je le pense dans les versions précédentes) sur la dernière ligne car les mots clés "en tant que" ne prennent pas en compte les opérateurs de conversion. La ligne string cast = (string)f; fonctionne cependant très bien.

18
Patrik Hägne

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

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

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

12
Anton Gogolev

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 transtyper. Espérons que ces moulages sont très rares. Sinon, vous devrez probablement repenser certaines de vos interfaces.

10
toad

Ne tenez pas compte des conseils de Jon Skeet, évitez les motifs de test et de distribution, à savoir

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

L'idée que cela coûte plus qu'un test 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 exécuté de vrais tests , et test-and-cast est en fait plus rapide que la comparaison cast-and-null, et c'est plus sûr aussi car vous n'avez pas la possibilité d'avoir une référence null dans le portée en dehors de la si le casting devrait échouer.

Si vous voulez une raison pour laquelle le test et la diffusion est plus rapide, ou du moins pas plus lent, il existe une raison simple et complexe.

Simple: même des compilateurs naïfs vont fusionner 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, l'un pour le test de type et la conversion en null en cas d'échec, l'autre pour la vérification de nullité elle-même. À tout le moins, ils optimiseront à la fois un test et une branche, de sorte que les tests ne seront ni plus lents ni plus rapides que les tests cast et null.

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

Alors, s’il vous plaît, s’IL VOUS PLAÎT, laissez ce test "cast-and-null-test est meilleur que test-and-cast" DIE. S'IL VOUS PLAÎT. tester et lancer est à la fois plus sûr et plus rapide.

9
naasking

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 à lancer un objet, par exemple. IMyInterface à MyClass. L'avantage 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 que cela n'a aucun sens d'utiliser IMyInterface, car si vous alimentez votre code avec d'autres classes qui implémentent IMyInterface, votre code serait cassé ...

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

4
f3lix

Si la conversion échoue, le mot clé "en tant que" ne génère pas d'exception; il définit la variable sur null (ou sur sa valeur par défaut pour les types de valeur).

4
TheSmurf

L'opérateur as ne peut être utilisé que sur des types de référence, il ne peut pas être surchargé et renvoie null si l'opération échoue. Il ne jettera jamais une exception.

Le transtypage peut être utilisé sur tous les types compatibles, il peut être surchargé et une exception sera levée si l'opération échoue.

Le choix de l’utilisation dépend des circonstances. Il s'agit principalement de savoir si vous souhaitez lancer une exception lors d'une conversion ayant échoué.

3
Jeffrey L Whitledge

Ma réponse concerne uniquement la rapidité dans les cas où nous ne vérifions pas le type et les null après la diffusion. 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.

1
CoperNick

Si vous utilisez les PIA d'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;

Casting est acceptable pour le ciblage de .NET 2.0:

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

Lorsque vous ciblez .NET 4.X, les erreurs sont les suivantes:

erreur CS0656: membre manquant du compilateur requis 'Microsoft.CSharp.RuntimeBinder.Binder.Convert'

erreur CS0656: membre du compilateur requis manquant 'Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo.Create'

1
Olivier MATROT

Outre tout ce qui a déjà été exposé ici, je viens de découvrir une différence pratique qui, à mon avis, mérite d’être soulignée, entre le casting explicite

var x = (T) ...

par rapport à l’utilisation de l’opérateur as.

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

Bottom line: GenericCaster2 ne fonctionnera pas avec les types struct. GenericCaster le fera.

1
Veverke

Le mot clé as fonctionne de la même manière qu'une conversion explicite entre les types de référence compatibles, à la différence majeure qu'il ne déclenche pas d'exception si la conversion échoue. Au lieu de cela, il produit une valeur nulle dans la variable cible. Comme les exceptions sont très coûteuses en termes de performances, cette méthode est considérée comme une bien meilleure méthode de casting.

0
Cerebrus
0
juFo

Cela dépend. Voulez-vous vérifier la valeur null après avoir utilisé "en tant que" ou préférez-vous que votre application lève une exception?

Ma règle générale est que si je m'attends toujours à ce que la variable soit du type que j'attends au moment où je le souhaite, j'utilise un transtypage. S'il est possible que la variable ne transtypera pas ce que je veux et que je sois prêt à gérer les valeurs NULL à partir de as, j'utiliserai comme.

0
Darryl Braaten

Le problème du PO est limité à une situation de casting spécifique. Le titre couvre beaucoup 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
}
0
Tobias Knauss

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 un objet doit être de type IMyInterface et que ce n'est pas le cas, c'est certainement un problème. Il est préférable d’obtenir une erreur le plus tôt possible car une erreur exacte sera corrigée au lieu de corriger ses effets secondaires.

Mais si vous utilisez des méthodes qui acceptent object en tant que paramètre, vous devez vérifier son type exact avant d'exécuter du code. Dans ce cas, as serait utile pour éviter InvalidCastException.

0
Oleg