2009-06-22 12 views
15

Ricevo uno strano comportamento utilizzando la funzione C# List.Sort incorporata con un comparatore personalizzato.List.Sort in C#: comparatore chiamato con oggetto nullo

Per qualche motivo a volte chiama il metodo Confronto della classe di confronto con un oggetto nullo come uno dei parametri. Ma se controllo la lista con il debugger non ci sono oggetti nulli nella collezione.

La mia classe di confronto si presenta così:

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; 
    } 
} 

Questo permette un delegato da passare al metodo list.sort, in questo modo:

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

Così il sopra delegato getterà un null eccezione di riferimento per il parametro x, anche se nessun elemento di mylist è nullo.

UPDATE: Sì, sono assolutamente sicuro che è il parametro x gettando eccezione riferimento null!

UPDATE: Invece di utilizzare il metodo list.sort del quadro, ho cercato un metodo di ordinamento personalizzata (cioè nuova BubbleSort() Sort (mylist).) E il problema è andato via. Come sospettavo, il metodo List.Sort passa nullo al comparatore per qualche motivo.

+2

Re la modifica - Non credo che tu abbia qualcosa di riproducibile che possiamo guardare? (btw, se fossi tu - era un downvote davvero garantito?) –

+1

Concordato - un programma breve ma completo che riproduca il problema sarebbe molto utile. Dubito molto che si tratti di un bug in List.Sort. –

risposta

18

Questo problema si verifica quando la funzione di confronto non è coerente, tale che x < y non implica sempre y < x. Nel tuo esempio, dovresti controllare come vengono confrontate due istanze del tipo di SomeProp.

Ecco un esempio che riproduce il problema. Qui, è causato dalla funzione di comparazione patologica "compareStrings". Dipende dallo stato iniziale della lista: se cambi l'ordine iniziale in "C", "B", "A", allora non ci sono eccezioni.

Non definirei un bug nella funzione di ordinamento: è semplicemente un requisito che la funzione di confronto sia coerente.

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

In VB.NET questo non genera un errore. Quanto è strano? –

+2

non è un bug, ma sarebbe bello se l'eccezione fosse "InconsistentComparisionMethodException" invece del puntatore nullo standard ex. quando non ci sono valori nulli nell'array ... molto confuso – serine

2

Sei sicuro che il problema non è che SomeProp è null?

In particolare, con stringhe o valori Nullable<T>.

con le stringhe, sarebbe meglio usare:

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

(edit)

Per un wrapper null-sicuro, è possibile utilizzare Comparer<T>.Default - ad esempio, per ordinare un elenco da una proprietà :

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

Spiacente, è sicuramente il parametro x che è null, non la sua proprietà. Non voglio che questo sia nullo - non dovrebbe essere nullo. – cbp

0

La risposta di Marc è utile. Sono d'accordo con lui sul fatto che NullReference debba chiamare CompareTo su una proprietà null. Senza bisogno di una classe di estensione, si può fare:

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

dove SomePropType è il tipo di SomeProp

0

Per scopi di debug, volete che il vostro metodo per essere null-sicura. (o almeno, prendi l'eccezione null-ref e gestiscila in modo hard-coded). Quindi, utilizzare il debugger per vedere quali altri valori vengono confrontati, in quale ordine e quali chiamate hanno esito positivo o negativo.

Quindi troverete la risposta, e quindi è possibile rimuovere la null-sicurezza.

0

Puoi eseguire questo codice ...

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

ci siamo imbattuti in questo problema me stesso, e ha scoperto che si era legato a una proprietà NaN nel mio ingresso. Ecco un banco di prova minima che dovrebbe produrre eccezione:

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

troppo Mi sono imbattuto in questo problema (riferimento null essere passato alla mia implementazione IComparer personalizzato) e, infine, scoperto che il problema era dovuto alla funzione di confronto utilizzando incoerente.

Questa era la mia implementazione IComparer iniziale:

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'errore in questo codice è stato che mettono a confronto sarebbe tornato -1 ogni volta che uno dei valori non possono essere letti correttamente (nel mio caso è stato a causa di erroneamente formattato rappresentazioni di stringa di valori numerici, quindi TryParse ha sempre avuto esito negativo).

Si noti che nel caso in cui sia x che y sono stati formattati in modo errato (e quindi TryParse non è riuscito su entrambi), chiamare Compare (x, y) e Compare (y, x) darebbe lo stesso risultato: -1. Questo penso che fosse il problema principale. Durante il debug, Compare() sarebbe passato a un puntatore a stringa nullo come uno dei suoi argomenti in qualche punto, anche se la raccolta ordinata non conteneva una stringa nulla.

Non appena ho risolto il problema TryParse e assicurato la coerenza della mia implementazione, il problema è andato via e Compare non è stato più passato ai puntatori nulli.

Problemi correlati