2009-06-22 2 views
15

aufgerufen Ich bekomme seltsames Verhalten mit der eingebauten C# List.Sort-Funktion mit einem benutzerdefinierten Vergleich.List.Sort in C#: Comparer wird mit Nullobjekt

Aus irgendeinem Grund ruft es manchmal Compare-Methode der Vergleichsklasse mit einem Null-Objekt als einen der Parameter auf. Aber wenn ich die Liste mit dem Debugger überprüfe, gibt es keine Nullobjekte in der Sammlung.

Meine comparer Klasse sieht wie folgt aus:

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

Dies ermöglicht ein Delegierter der List.Sort Methode übergeben werden, wie folgt aus:

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

So die oben Delegierten wird eine Null-Wurf Referenzausnahme für den x Parameter, obwohl keine Elemente von mylist null sind.

UPDATE: Ja, ich bin absolut sicher, dass es Parameter ist x die NULL-Verweis Ausnahme werfen!

UPDATE: Statt den Rahmen des List.Sort Methode zu verwenden, habe ich versucht, eine benutzerdefinierte Sortierverfahren (das heißt neuer BubbleSort() Sort (mylist).) Und das Problem ging weg. Wie ich vermutete, übergibt die List.Sort-Methode aus irgendeinem Grund Null an den Vergleicher.

+2

Re die Bearbeitung - ich nehme an, Sie haben nichts reproduzierbar können wir betrachten? (übrigens, wenn du es wärst - war ein Downvote wirklich gerechtfertigt?) –

+1

Einverstanden - ein kurzes, aber vollständiges Programm, das das Problem reproduziert, wäre sehr praktisch. Ich bezweifle sehr, dass dies ein Fehler in List.Sort ist. –

Antwort

18

Dieses Problem tritt auf, wenn die Vergleichsfunktion ist nicht konsistent, so dass x < y nicht immer y < x bedeuten. In Ihrem Beispiel sollten Sie prüfen, wie zwei Instanzen des Typs SomeProp verglichen werden.

Hier ist ein Beispiel, das das Problem reproduziert. Hier wird es durch die pathologische Vergleichsfunktion "compareStrings" verursacht. Es hängt vom Anfangszustand der Liste ab: Wenn Sie die ursprüngliche Reihenfolge in "C", "B", "A" ändern, gibt es keine Ausnahme.

Ich würde dies nicht einen Fehler in der Sort-Funktion nennen - es ist einfach eine Voraussetzung, dass die Vergleichsfunktion konsistent ist.

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 wirft dies keinen Fehler. Wie seltsam ist das? –

+2

kein Bug, aber wäre nett, wenn die Ausnahme wäre "InconsistentComparisionMethodException" anstelle von Standard-Nullzeiger ex. wenn es im Array keine Nullwerte gibt ... sehr verwirrend – serine

2

Sind Sie sicher, das Problem ist nicht, dass SomePropnull ist?

Insbesondere mit Strings oder Nullable<T> Werte.

mit Streichern, wäre es besser, zu verwenden:

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

(edit)

Für einen Null-safe-Wrapper können Sie Comparer<T>.Default verwenden - zum Beispiel eine Liste von einer Eigenschaft zu sortieren :

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

Sorry, es ist definitiv der Parameter x, der null ist, nicht seine Eigenschaft. Ich möchte nicht, dass dies null-sicher ist - es sollte nicht null sein. – cbp

0

Marc Antwort ist nützlich. Ich stimme ihm zu, dass die NullReference CompareTo auf einer Null-Eigenschaft aufrufen soll. Ohne eine Verlängerung Klasse zu benötigen, können Sie tun:

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

wo SomePropType die Art der SomeProp ist

0

Für Debugging-Zwecke, Sie möchten, dass Ihre Methode null-sicher zu sein. (Oder fangen Sie die Null-Referenz-Ausnahme ab und behandeln Sie sie auf eine hart codierte Weise). Verwenden Sie dann den Debugger, um zu beobachten, welche anderen Werte in welcher Reihenfolge miteinander verglichen werden und welche Aufrufe erfolgreich sind oder fehlschlagen.

Dann finden Sie Ihre Antwort, und Sie können dann die Null-Sicherheit entfernen.

0

Können Sie diesen Code ausführen ...

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

ich auf dieses Problem gestoßen mich, und stellte fest, dass es zu einer NaN Eigenschaft in meinem Eingang verbunden war. Hier ist ein minimaler Testfall, den die Ausnahme produzieren sollte:

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

Auch ich habe über dieses Problem kommen (null Bezug auf meine benutzerdefinierten IComparer Implementierung übergeben wird) und fand schließlich heraus, dass das Problem mit inkonsistenter Vergleichsfunktion zurückzuführen ist.

Diese meine erste IComparer Implementierung war:

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

Der Fehler in diesem Code das würde zurückkehren wurde Vergleichen -1, wenn einer der Werte nicht richtig (in meinem Fall analysiert werden konnte, es war wegen falsch formatiert String-Darstellungen von numerischen Werten, so dass TryParse immer fehlgeschlagen ist).

Beachten Sie, dass im Fall, dass sowohl x als auch y falsch formatiert wurden (und daher TryParse bei beiden fehlschlug), Compare (x, y) und Compare (y, x) dasselbe Ergebnis liefern würden: -1. Das war meiner Meinung nach das Hauptproblem. Beim Debuggen wird Compare() an einem bestimmten Punkt als Argument übergeben, obwohl die sortierte Auflistung keine Null-Zeichenfolge enthielt.

Sobald ich das TryParse-Problem behoben und die Konsistenz meiner Implementierung sichergestellt hatte, ging das Problem weg und Compare wurde nicht mehr Nullzeiger übergeben.