2009-12-01 5 views
4

Je viens de terminer le débogage d'un problème, où notre programme s'est écrasé sur un serveur de production, mais jamais sur les machines de développement.Le même code agit différemment sur différentes machines - quelle en serait la cause? (Problème de version de CLR?)

J'ai fait ce petit programme que je pouvais reproduire le problème avec:

using System; 
using System.Collections.Generic; 
using System.Linq; 

namespace RunTimeBug 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      var coll = new Collection {{"Test", new Data()}, {"Test2", new Data()}}; 

      var dataSequence = coll.Cast<Data>().ToList(); 
      Console.WriteLine(dataSequence.Count); 
     } 
    } 

    class Collection : Dictionary<string,Data>, IEnumerable<Data> 
    { 
     public new IEnumerator<Data> GetEnumerator() 
     { 
      foreach(var v in Values) 
       yield return v; 
     } 
    } 

    class Data { } 
} 

Lors de l'exécution sur ma machine, ce code imprime « 2 ». Lors de l'exécution sur le serveur de production, il échoue à l'exception suivante:

Unhandled Exception: System.InvalidCastException: Unable to cast object of type 'System.Collections.Generic.KeyValuePair 
`2[System.String,RunTimeBug.Data]' to type 'RunTimeBug.Data'. 
    at System.Linq.Enumerable.<CastIterator>d__b0`1.MoveNext() 
    at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection) 
    at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source) 
    at RunTimeBug.Program.Main(String[] args) 

La seule différence que je peux trouver sur ces machines, est que le Runtime CLR est une version 2.0.50727.4927 sur des machines, où il travaille, et la version 2.0.50727.1433 sur les machines où cela ne fonctionne pas. Pour autant que je sache, la méthode d'extension Cast obtient la mauvaise version de IEnumerable sur les anciennes machines, mais la "bonne" sur les nouvelles machines. Quelqu'un peut-il expliquer pourquoi je vois cela?

Qu'est-ce qui a changé entre les deux versions de CLR Runtime, qui pourrait en être la cause?

S'il vous plaît noter, j'ai déjà déployé un correctif, et je suis conscient que la classe Collection dans le code ci-dessus est de mauvaise conception, car il implémente 2 IEnumerable différents. Cela a été trouvé "dans la nature" dans notre produit, donc j'aimerais vraiment connaître la cause exacte.

Répondre

3

Je pense que le correctif mentionné dans l'article auquel Jason a lié a quelque chose à voir avec votre code fonctionnant sur des versions ultérieures. Je n'ai pas accès à la version pré-SP1 de Enumerable.Cast, donc c'est un peu difficile à deviner. Une chose est sûre: une fois que le code est entré dans Enumerable.CastIterator(), il est garanti que cela ne fonctionnera pas dans votre cas. Cela configure un itérateur pour convertir IEnumerable en IEnumerable <> et que l'itérateur appelle GetEnumerator() pour initialiser le bloc d'itérateur. Cela peut seulement appeler Dictionary.GetEnumerator(), pas le vôtre. La conversion du KeyValuePair que cet IEnumerable retourne en données échouera bien sûr, comme l'indique l'exception.

La version actuelle de Enumerable.Cast() essaie d'abord de convertir IEnumerable en IEnumerable <>. Et ça marche. CastIterator() n'est pas utilisé.

+0

Bref, si vous réimplémentez une interface, assurez-vous de réimplémenter tous les membres «liés». Dans le cas de 'IEnumerable ', cela signifie implémenter 'IEnumerable .GetEnumerator()' et 'IEnumerable.GetEnumerator()'. –

2

Pourquoi pensez-vous qu'il existe une "bonne" version à choisir? Personnellement, je serais heureux qu'il jette une erreur de compilateur ambigu à ce stade. Plutôt que Cast, peut-être juste un casting régulier?

var dataSequence = ((IEnumerable<Data>)coll).ToList(); 

Cast<T> jette les données, pas la variable.

BTW, vous recompilez pour chacun? Je me demande si le problème est le compilateur plutôt que le CLR ou BCL.

+0

J'aurais aussi préféré un avertissement de compilateur dans cette situation - alors nous l'aurions attrapé plus tôt - quand je parle de la "bonne" version, je voulais dire la version, que le développeur d'origine avait l'intention. Je ne compile qu'une seule version du programme, c'est le même exécutable s'exécutant sur les différentes machines. – driis

+0

Il y a clairement une bonne version de l'implémentation d'interface à choisir ici, et c'est la plus "dérivée". –

+0

Le plus dérivé quoi? 'IEnumerable ' vs 'IEnumerable >' - ni est "plus dérivé". On peut soutenir que celui avec moins de types génériques est un meilleur candidat, mais je devrais lire le spec très soigneusement (ou demander à l'oracle). –

2

CLR version 2.0.50727.1433 est .NET 2.0 SP1 alors que CLR version 2.0.50727.4927 inclut .NET 3.5 SP1 (pour référence, voir le Version History of the CLR).

Le comportement de Enumerable.Cast est passé de .NET 3.5 à .NET 3.5 SP1 comme indiqué here.

+0

pour les structures typées explicites; l'exemple ci-dessus est une classe typée implicite –

+0

Le comportement a été modifié pour corriger le problème avec la conversion de type valeur, mais cette modification a elle-même modifié le comportement pour les types de référence. Voir la réponse de nobugz pour une explication. –