37 votes

Pourquoi ce Linq Cast échoue-t-il lors de l'utilisation de ToList ?

Considérez cet exemple artificiel et trivial :

    var foo = new byte[] {246, 127};
    var bar = foo.Cast<sbyte>();
    var baz = new List<sbyte>();
    foreach (var sb in bar)
    {
        baz.Add(sb);
    }
    foreach (var sb in baz)
    {
        Console.WriteLine(sb);
    }

Grâce à la magie du complément à deux, -10 et 127 sont imprimés sur la console. Jusqu'ici tout va bien. Les personnes aux yeux aiguisés verront que je suis en train d'itérer sur un énumérable et de l'ajouter à une liste. Cela ressemble à ToList :

    var foo = new byte[] {246, 127};
    var bar = foo.Cast<sbyte>();
    var baz = bar.ToList();
    //Nothing to see here
    foreach (var sb in baz)
    {
        Console.WriteLine(sb);
    }

Sauf que cela ne fonctionne pas. J'obtiens cette exception :

Type d'exception : System.ArrayTypeMismatchException

Message : Le type de tableau source ne peut pas être assigné au type de tableau de destination.

Je trouve cette exception très particulière car

  1. ArrayTypeMismatchException - Je ne fais rien avec les tableaux, moi-même. Il semble s'agir d'une exception interne.
  2. El Cast<sbyte> fonctionne bien (comme dans le premier exemple), c'est lorsque l'on utilise ToArray o ToList le problème se présente.

Je vise .NET v4 x86, mais le même problème se produit en 3.5.

Je n'ai pas besoin de conseils sur la manière de résoudre le problème, j'y suis déjà parvenu. Ce que je veux savoir, c'est pourquoi ce comportement se produit en premier lieu ?

EDITAR :

Encore plus étrange, l'ajout d'une instruction de sélection sans signification provoque le déclenchement de la fonction ToList pour fonctionner correctement :

var baz = bar.Select(x => x).ToList();

29voto

Jon Skeet Points 692016

Ok, cela dépend vraiment de quelques bizarreries combinées :

  • Bien qu'en C#, vous ne pouvez pas utiliser une valeur de type byte[] à un sbyte[] directement, le CLR le permet :

    var foo = new byte[] {246, 127};
    // This produces a warning at compile-time, and the C# compiler "optimizes"
    // to the constant "false"
    Console.WriteLine(foo is sbyte[]);
    
    object x = foo;
    // Using object fools the C# compiler into really consulting the CLR... which
    // allows the conversion, so this prints True
    Console.WriteLine(x is sbyte[]);
  • Cast<T>() est optimisé de telle sorte que s'il pense qu'il n'a pas besoin de faire quoi que ce soit (via un bouton is comme le contrôle ci-dessus), il renvoie la référence originale - c'est donc ce qui se passe ici.

  • ToList() délègue au constructeur de List<T> prendre un IEnumerable<T>

  • Ce constructeur est optimisé pour ICollection<T> à utiliser CopyTo ... et c'est ce qui échoue. Voici une version qui n'a pas d'appel de méthode autre que CopyTo :

    object bytes = new byte[] { 246, 127 };
    
    // This succeeds...
    ICollection<sbyte> list = (ICollection<sbyte>) bytes;
    
    sbyte[] array = new sbyte[2];
    
    list.CopyTo(array, 0);

Maintenant, si vous utilisez un Select à tout moment, vous ne vous retrouvez pas avec une ICollection<T> donc il passe par le légitime (pour le CLR). byte / sbyte pour chaque élément, plutôt que d'essayer d'utiliser l'implémentation de l'option "tableau" de la méthode CopyTo .

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