2009-03-11 7 views
2

J'ai pensé qu'il était étrange que C# me laisse appeler trier sur ma classe et ne spécifie pas un moyen de les trier ni d'écrire une surcharge de comparaison. Quand je courais ce code cette erreur apparaissaitPourquoi C# permet-il de compiler le code de tri quand il ne sait pas comment trier

List<MyClass> myClassArray= new List<MyClass>(); 
//myClassArray.add(...); 
myClassArray.Sort(); 

An unhandled exception of type 'System.InvalidOperationException' occurred in mscorlib.dll 

Additional information: Failed to compare two elements in the array. 

Pourquoi C# me laisse compiler ce code quand il ne marche pas savoir comment régler ce problème! -modifier

Le Codex demande pourquoi il fait ceci. J'ai écrit une théorie sur pourquoi il le fait dans mes commentaires. Voici un exemple de code.

class A : IComparable<A> 
{ 
    public int CompareTo(A a) { return 0; } 
} 
class C //: IComparable<A> 
{ 
    public int CompareTo(A a) { return 0; } 
} 
    static void test() 
    { 
     A a = new A(); 
     bool b; 
     C c = new C(); 

     object o = a; 
     IComparable<A> ia = (IComparable<A>)o; 
     b = ia == ia; 

     o = c; 
     IComparable<A> ic = (IComparable<A>)o; 
     b = ic == ic; 

     //uncomment this to get a compile time error 
     //IComparable<A> ic2 = c; 
     return; 
    } 

Si vous décommentez la ligne avant le retour, vous obtiendrez une erreur de compilation. Lorsque vous décommentez IComparable dans la classe c, il compilera et travaillera.

+0

BTW solution de tri ajoute: IComparable à votre définition de classe et définissant la fonction int CompareTo (T). –

+0

@ acidzombie24: Je suis d'accord avec la question "Je ne comprends pas comment ça se compile?" Je n'ai pas trouvé de réponse convaincante. Pourriez-vous s'il vous plaît partager le vôtre? – Codex

+0

Codex: Jugement par jeffamaphone et Marc Gravell répondre à la seule conclusion que j'ai trouvé était C# garde la trace du type de toutes les vars pendant l'exécution (c'est pourquoi l'objet fonctionne?) Avec le nom et la signature de la méthode. En raison de la disponibilité des types au moment de l'exécution, je suppose qu'ils essaient de le typecasting à –

Répondre

13

Il n'y a aucune contrainte sur le paramètre générique de la liste <T> qui l'oblige à mettre en œuvre IComparable <T>. S'il y en avait, cela garantirait (en quelque sorte) que les éléments pourraient être triés, mais vous ne seriez pas en mesure d'utiliser List <T> pour contenir tout ce qui n'a pas implémenté IComparable. Et puisque vous ne trierez probablement pas toutes les listes que vous créez, c'est la bonne décision.

+0

Pourquoi myClassArray a-t-il une méthode de tri? pourquoi ça ne me force pas à faire du tri (myList, myCompare); ou quelque chose. Je ne comprends pas comment ça se compile. Est-ce une méthode virtuelle qui jette si elle est vide? est-ce que cela a quelque chose à voir avec les types dynamiques? ta réponse est géniale mais je veux plus d'infos. –

+0

nevermind j'ai eu ma réponse –

2

Le tri doit être vérifié pour voir si votre objet implémente IComparable. C'est une vérification du temps d'exécution, et puisque vous ne l'appliquez probablement pas, le comparateur par défaut ne sait pas quoi faire avec vos objets, donc il lance l'exception.

Il le laisse compiler car il ne s'agit pas d'une fonctionnalité de langage, c'est une fonctionnalité de framework.

2

Juste pour plus d'informations; essentiellement, il utilise Comparer<T>.Default pour faire les comparaisons. Cela implémente IComparer<T> et peut comparer 2 objets de type T. L'implémentation réelle est sélectionnée la première fois que vous le demandez (par T); Il existe un certain nombre de modèles que le framework utilise pour choisir l'implémentation - par exemple, les classes, les structures "régulières" et Nullable<T> sont toutes gérées séparément. De même, il fait des choix selon que T implémente IComparable<T>, IComparable, ou aucun (dans ce cas, il déclenche une exception).

Ceci fournit un moyen très facile de faire un tri "de type canard". De même, il existe également un EqualityComparer<T>.Default qui vérifie IEquatable<T>, par défaut object.Equals sinon.

0

C# aurait pu avoir juste un peu cuit dans la notion dans System.Object sur la façon dont vous commandez des objets comme il le faisait avec Equals pour les comparer pour l'identité.

Malheureusement, cela conduit à des préoccupations au sujet intentionnalité contre extensionnalité, localizationm etc.

Il existe une interface IComparable<T>, mais les types de valeur intégrée ne peut implémenter des interfaces comme ça. Par conséquent, il n'y a pas de bon moyen de regarder un type au moment de la compilation et de savoir définitivement s'il a un ordre significatif. = (

Le mécanisme qui a évolué en C# est d'utiliser l'instance IComparer<T> qui est retournée par Comparer<T>.Default et d'obtenir une erreur d'exécution lorsque vous essayez de trier quelque chose qui manque un ordre.En autorisant plusieurs IComparer s et IComparer<T> s, vous pouvez avoir une idée de plusieurs commandes alternatives qui fonctionnent sur le même type, de sorte que tout va bien, mais tout ne va pas bien.

interne, C# utilise un tas de règles pour trouver Comparer<T>.Default, qu'il gère différemment si T est une instance de IComparable<T> ou est de la forme Nullable<T>, etc.

par exemple le code de system/collections/generic/comparer.cs:

public static Comparer<T> Default { 
     get { 
      Comparer<T> comparer = defaultComparer; 
      if (comparer == null) { 
       comparer = CreateComparer(); 
       defaultComparer = comparer; 
      } 
      return comparer; 
     } 
    } 

    private static Comparer<T> CreateComparer() { 
     Type t = typeof(T); 
     // If T implements IComparable<T> return a GenericComparer<T> 
     if (typeof(IComparable<T>).IsAssignableFrom(t)) { 
      //return (Comparer<T>)Activator.CreateInstance(typeof(GenericComparer<>).MakeGenericType(t)); 
      return (Comparer<T>)(typeof(GenericComparer<int>).TypeHandle.CreateInstanceForAnotherGenericParameter(t)); 
     } 
     // If T is a Nullable<U> where U implements IComparable<U> return a NullableComparer<U> 
     if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable<>)) { 
      Type u = t.GetGenericArguments()[0]; 
      if (typeof(IComparable<>).MakeGenericType(u).IsAssignableFrom(u)) { 
       //return (Comparer<T>)Activator.CreateInstance(typeof(NullableComparer<>).MakeGenericType(u)); 
       return (Comparer<T>)(typeof(NullableComparer<int>).TypeHandle.CreateInstanceForAnotherGenericParameter(u));     
      } 
     } 
     // Otherwise return an ObjectComparer<T> 
     return new ObjectComparer<T>(); 
    } 

Essentiellement, cela permet d'ajouter Microsoft progressivement des cas particuliers pour leur propre code, mais malheureusement, le code qui en résulte simplement en utilisant Comparer<T>.Default pour une instance personnalisée IComparable<T> est assez abyssale. La méthode Sort sur IList<T>, suppose que Comparer<T>.Default trouvera quelque chose qu'il peut utiliser pour comparer vos objets, mais il n'a aucun moyen de regarder le type T et dire qu'il le fait, donc il suppose qu'il est sûr et ne réalise que plus tard au moment de l'exécution que MyClass ne joue pas.

Questions connexes