43 votes

Pourquoi mon tableau C # perd-il les informations de signe de type lors de la conversion en objet?

Enquête sur un bug, j'ai découvert que c'était en raison de cette étrangeté en c#:

sbyte[] foo = new sbyte[10];
object bar = foo;
Console.WriteLine("{0} {1} {2} {3}",
        foo is sbyte[], foo is byte[], bar is sbyte[], bar is byte[]);

La sortie est à "True False True True", alors que je me serais attendu "bar is byte[]" de Fausse déclaration. Apparemment, la barre est à la fois un byte[] et sbyte[]? La même chose se passe pour les autres signed/unsigned types comme Int32[] vs UInt32[], mais pas pour dire Int32[] vs Int64[].

Quelqu'un peut expliquer ce comportement? C'est dans .NET 3.5.

70voto

Eric Lippert Points 300275

Mise à JOUR: j'ai utilisé cette question comme la base d'une entrée de blog, ici:

http://blogs.msdn.com/ericlippert/archive/2009/09/24/why-is-covariance-of-value-typed-arrays-inconsistent.aspx

Voir le blog de commentaires pour une discussion étendue de ce problème. Merci pour la grande question!


Vous avez trébuché à travers un intéressant et malheureux incohérence entre la CLI type de système et le C# type de système.

La CLI a la notion de "compatibilité d'affectation". Si une valeur x de données de type S est "affectation compatible" avec un emplacement de stockage de données de type T, alors vous pouvez magasin de x dans y. Si non, alors ce n'est pas vérifiable code et le vérificateur va la rejeter.

La CLI type de système est dit, par exemple, que les sous-types du type de référence sont d'affectation compatible avec aux supertypes de type référence. Si vous avez une chaîne, vous pouvez la stocker dans une variable de type objet, parce que les deux sont des types référence et le string est un sous-type de l'objet. Mais l'inverse n'est pas vrai; aux supertypes ne sont pas d'affectation compatible avec les sous-types. Vous ne pouvez pas coller quelque chose seulement connue pour être l'objet dans une variable de type chaîne de caractères sans premier casting elle.

Fondamentalement, la "cession compatible" signifie "il est logique de s'en tenir exacte des bits dans cette variable". L'affectation de la source de la valeur à la variable cible doit être "la préservation de la représentation". Voir mon article sur que pour plus de détails:

http://ericlippert.com/2009/03/03/representation-and-identity/

L'une des règles de la CLI est "si X est d'affectation compatible avec de Y, alors X[] est d'affectation compatible avec Y[]".

Qui est, les tableaux sont covariants à l'égard de la compatibilité d'affectation. C'est en fait une fracture de la nature de la covariance; voir mon article sur ce pour plus de détails.

http://blogs.msdn.com/ericlippert/archive/2007/10/17/covariance-and-contravariance-in-c-part-two-array-covariance.aspx

Ce n'est PAS une règle de C#. C#'s de la matrice de covariance de la règle est "si X est un type de référence implicitement convertible type de référence de Y, alors X[] est implicitement convertible Y[]". C'est subtilement différente de la règle, et donc votre situation confuse.

Dans la CLI, uint int assignation compatible. Mais en C#, la conversion entre int et uint est EXPLICITE, non pas IMPLICITE, et ce sont des types de valeur, pas de types de référence. Donc, en C#, il n'est pas légal de convertir un int[] pour un uint[].

Mais il EST légal dans la CLI. Alors maintenant, nous sommes confrontés à un choix.

1) mettre en Œuvre le "est" de sorte que, lorsque le compilateur ne peut pas déterminer la réponse de manière statique, en effet, il appelle une méthode qui vérifie tous les C# règles pour l'identité de la préservation de la convertibilité. C'est lent, et 99,9% du temps correspond à ce que les règles CLR sont. Mais nous prenons les performances afin d'être à 100% conforme avec les règles de C#.

2) mettre en Œuvre le "est" de sorte que, lorsque le compilateur ne peut pas déterminer la réponse statique, il ne l'incroyablement rapide CLR cession de contrôle de compatibilité, et de vivre avec le fait qu'il dit que une uint[] est un int[], même si ce ne serait légale en C#.

Nous avons choisi ce dernier. Il est regrettable que le C# et le CLI spécifications sont en désaccord sur ce point mineur, mais nous sommes prêts à vivre avec l'incohérence.

10voto

Benjamin Wegman Points 370

Passé l'extrait à travers le réflecteur:

 sbyte[] foo = new sbyte[10];
object bar = foo;
Console.WriteLine("{0} {1} {2} {3}", new object[] { foo != null, false, bar is sbyte[], bar is byte[] });
 

Le compilateur C # optimise les deux premières comparaisons ( foo is sbyte[] et foo is byte[] ). Comme vous pouvez le constater, ils ont été optimisés à foo != null et simplement toujours false .

4voto

Ben M Points 14458

Aussi intéressant:

     sbyte[] foo = new sbyte[] { -1 };
    var x = foo as byte[];    // doesn't compile
    object bar = foo;
    var f = bar as byte[];    // succeeds
    var g = f[0];             // g = 255
 

-1voto

cdm9002 Points 1174

Le résultat est sûrement correct. bar "est" sbyte [] et byte [], car il est compatible avec les deux, puisque bar est simplement un objet, il "pourrait être" signé ou non signé.

"is" est défini comme "expression peut être transtypé en type".

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