2009-06-22 10 views
15

Je reçois un comportement étrange en utilisant la fonction intégrée C# List.Sort avec un comparateur personnalisé.List.Sort en C#: Appelé avec un objet nul

Pour une raison quelconque, il appelle parfois la méthode Compare de la classe du comparateur avec un objet nul comme l'un des paramètres. Mais si je vérifie la liste avec le débogueur, il n'y a aucun objet nul dans la collection.

Ma classe comparateur ressemble à ceci:

public class DelegateToComparer<T> : IComparer<T> 
{ 
    private readonly Func<T,T,int> _comparer; 

    public int Compare(T x, T y) 
    { 
     return _comparer(x, y); 
    } 

    public DelegateToComparer(Func<T, T, int> comparer) 
    { 
     _comparer = comparer; 
    } 
} 

Cela permet à un délégué à passer à la méthode list.sort, comme ceci:

mylist.Sort(new DelegateToComparer<MyClass>(
    (x, y) => { 
     return x.SomeProp.CompareTo(y.SomeProp); 
    }); 

Ainsi, le délégué ci-dessus va jeter un nul exception de référence pour le paramètre x, même si aucun élément de mylist est nul.

MISE À JOUR: Oui, je suis absolument sûr qu'il est le paramètre x lancer l'exception de référence null!

MISE À JOUR: Au lieu d'utiliser la méthode list.sort de cadre, j'ai essayé une méthode de tri personnalisé (avec nouvelle BubbleSort() Trier (mylist).) Et le problème a disparu. Comme je le suspectais, la méthode List.Sort passe nullement au comparateur pour une raison quelconque.

+2

Re l'édition - Je ne pense pas que vous ayez quelque chose de reproductible que nous pouvons regarder? (btw, si c'était vous - une downvote était-elle vraiment justifiée?) –

+1

D'accord - un programme court mais complet reproduisant le problème serait très pratique. Je doute vraiment que ce soit un bug dans List.Sort. –

Répondre

18

Ce problème se produit lorsque la fonction de comparaison ne correspond pas, tel que x < y ne signifie pas toujours y < x. Dans votre exemple, vous devriez vérifier comment deux instances du type de SomeProp sont comparées.

Voici un exemple qui reproduit le problème. Ici, il est causé par la fonction de comparaison pathologique "compareStrings". Cela dépend de l'état initial de la liste: si vous changez l'ordre initial en "C", "B", "A", il n'y a pas d'exception.

Je n'appellerais pas ça un bug dans la fonction Sort - c'est simplement une exigence que la fonction de comparaison soit cohérente.

using System.Collections.Generic; 

class Program 
{ 
    static void Main() 
    { 
     var letters = new List<string>{"B","C","A"}; 

     letters.Sort(CompareStrings); 
    } 

    private static int CompareStrings(string l, string r) 
    { 
     if (l == "B") 
      return -1; 

     return l.CompareTo(r); 
    } 
} 
+0

Dans VB.NET cela ne génère pas d'erreur. C'est bizarre, c'est ça? –

+2

pas un bogue, mais serait bien si l'exception serait "InconsistentComparisionMethodException" au lieu du pointeur null standard ex. quand il n'y a pas de valeurs nulles dans le tableau ... très confus – serine

2

Etes-vous sûr que le problème n'est pas que SomeProp est null?

En particulier, avec des chaînes ou des valeurs Nullable<T>.

Avec des cordes, il serait préférable d'utiliser:

list.Sort((x, y) => string.Compare(x.SomeProp, y.SomeProp)); 

(modifier) ​​

Pour un emballage null-safe, vous pouvez utiliser Comparer<T>.Default - par exemple, pour trier la liste par une propriété :

using System; 
using System.Collections.Generic; 
public static class ListExt { 
    public static void Sort<TSource, TValue>(
      this List<TSource> list, 
      Func<TSource, TValue> selector) { 
     if (list == null) throw new ArgumentNullException("list"); 
     if (selector == null) throw new ArgumentNullException("selector"); 
     var comparer = Comparer<TValue>.Default; 
     list.Sort((x,y) => comparer.Compare(selector(x), selector(y))); 
    } 
} 
class SomeType { 
    public override string ToString() { return SomeProp; } 
    public string SomeProp { get; set; } 
    static void Main() { 
     var list = new List<SomeType> { 
      new SomeType { SomeProp = "def"}, 
      new SomeType { SomeProp = null}, 
      new SomeType { SomeProp = "abc"}, 
      new SomeType { SomeProp = "ghi"}, 
     }; 
     list.Sort(x => x.SomeProp); 
     list.ForEach(Console.WriteLine); 
    } 
} 
+1

Désolé c'est définitivement le paramètre x qui est nul, pas sa propriété. Je ne veux pas que ce soit null sûr - il ne devrait pas être nul. – cbp

0

La réponse de Marc est utile. Je suis d'accord avec lui que le NullReference est dû à l'appel de CompareTo sur une propriété nulle. Sans avoir besoin d'une classe d'extension, vous pouvez faire:

mylist.Sort((x, y) => 
     (Comparer<SomePropType>.Default.Compare(x.SomeProp, y.SomeProp))); 

où SomePropType est le type de SomeProp

0

Pour des fins de débogage, vous voulez que votre méthode soit nul en toute sécurité. (ou au moins, attraper l'exception null-ref, et la gérer de manière codée de manière irréversible). Ensuite, utilisez le débogueur pour voir quelles autres valeurs sont comparées, dans quel ordre, et quels appels réussissent ou échouent. Ensuite, vous trouverez votre réponse, et vous pourrez ensuite supprimer la sécurité-zéro.

0

Pouvez-vous exécuter ce code ...

mylst.Sort((i, j) => 
       { 
        Debug.Assert(i.SomeProp != null && j.SomeProp != null); 
        return i.SomeProp.CompareTo(j.SomeProp); 
       } 
     ); 
0

je suis tombé sur cette question moi-même, et a constaté qu'il était lié à une propriété NaN dans mon entrée. Voici un test minimal qui devrait produire l'exception:

public class C { 

    double v; 

    public static void Main() { 
     var test = 
      new List<C> { new C { v = 0d }, 
          new C { v = Double.NaN }, 
          new C { v = 1d } }; 
     test.Sort((d1, d2) => (int)(d1.v - d2.v)); 
    } 

} 
2

Moi aussi, je suis tombé sur ce problème (référence null étant passé à mon habitude la mise en œuvre IComparer) et ont finalement découvert que le problème était dû à l'utilisation de la fonction de comparaison incohérente.

Ce fut ma première mise en œuvre IComparer:

public class NumericStringComparer : IComparer<String> 
{ 
    public int Compare(string x, string y) 
    { 
     float xNumber, yNumber; 
     if (!float.TryParse(x, out xNumber)) 
     { 
      return -1; 
     } 
     if (!float.TryParse(y, out yNumber)) 
     { 
      return -1; 
     } 
     if (xNumber == yNumber) 
     { 
      return 0; 
     } 
     else 
     { 
      return (xNumber > yNumber) ? 1 : -1; 
     } 
    } 
} 

L'erreur dans ce code a été que Comparez renverrait -1 à chaque fois que l'une des valeurs ne pouvaient pas être analysées correctement (dans mon cas, il était dû à tort mis en forme représenter les valeurs numériques de façon à ce que TryParse ait toujours échoué). Notez que si x et y étaient mal formatés (et que TryParse échouait sur les deux), appeler Compare (x, y) et Compare (y, x) donnerait le même résultat: -1. Je pense que c'était le problème principal. Lors du débogage, Compare() se verrait passer un pointeur de chaîne null comme l'un de ses arguments à un certain moment même si la collection en cours de tri ne cotait pas une chaîne nulle.

Dès que j'avais résolu le problème TryParse et assuré la cohérence de mon implémentation, le problème disparaissait et Compare ne passait plus de pointeurs NULL.