2009-06-22 19 views
15

Tôi nhận được hành vi lạ bằng cách sử dụng hàm C# List.Sort dựng sẵn có trình so sánh tùy chỉnh.List.Sort in C#: so sánh được gọi với đối tượng null

Vì một số lý do, đôi khi nó gọi phương thức So sánh của lớp so sánh với đối tượng null là một trong các tham số. Nhưng nếu tôi kiểm tra danh sách với trình gỡ lỗi thì không có đối tượng null nào trong bộ sưu tập.

lớp Comparer của tôi trông như thế này:

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

Điều này cho phép một đại biểu được truyền cho phương thức List.Sort, như thế này:

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

Vì vậy, các đại biểu trên sẽ ném một null ngoại lệ tham chiếu cho thông số x, mặc dù không có thành phần nào của danh sách của tôi là không.

CẬP NHẬT: Có Tôi hoàn toàn chắc chắn rằng đó là tham số x ném ngoại lệ tham chiếu null!

UPDATE: Thay vì sử dụng phương pháp List.Sort của khuôn khổ, tôi đã thử một phương pháp tùy chỉnh loại (ví dụ: mới sắp xếp nổi bọt() Sắp xếp (mylist).) Và các vấn đề đi đi. Như tôi nghi ngờ, phương thức List.Sort vượt qua null để so sánh vì một lý do nào đó.

+2

Chỉnh sửa lại - Tôi không cho rằng bạn có bất kỳ thứ gì có thể tái sản xuất mà chúng tôi có thể xem? (btw, nếu đó là bạn - là một downvote thực sự bảo hành?) –

+1

Đồng ý - một chương trình ngắn nhưng đầy đủ tái tạo vấn đề sẽ rất tiện dụng. Tôi rất nghi ngờ rằng đây là một lỗi trong List.Sort. –

Trả lời

18

Vấn đề này sẽ xảy ra khi chức năng so sánh là không phù hợp, sao cho x < y không phải lúc nào bao hàm y < x. Trong ví dụ của bạn, bạn nên kiểm tra xem hai thể hiện của loại SomeProp đang được so sánh như thế nào.

Đây là ví dụ để tái tạo sự cố. Ở đây, nó được gây ra bởi chức năng so sánh bệnh lý "compareStrings". Nó phụ thuộc vào trạng thái ban đầu của danh sách: nếu bạn thay đổi thứ tự ban đầu thành "C", "B", "A", thì không có ngoại lệ.

Tôi sẽ không gọi đây là lỗi trong hàm Sắp xếp - chỉ đơn giản là yêu cầu rằng hàm so sánh nhất quán.

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

Trong VB.NET, điều này không gây ra lỗi. Lạ thật sao? –

+2

không phải là một lỗi, nhưng sẽ được tốt đẹp nếu ngoại lệ sẽ là "InconsistentComparisionMethodException" thay vì tiêu chuẩn null con trỏ cũ. khi không có giá trị null trong mảng ... rất khó hiểu – serine

2

Bạn có chắc chắn rằng vấn đề không phải là SomePropnull?

Cụ thể, với chuỗi hoặc Nullable<T> giá trị.

Với chuỗi, nó sẽ là tốt hơn để sử dụng:

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

(chỉnh sửa)

Đối với một wrapper null-an toàn, bạn có thể sử dụng Comparer<T>.Default - ví dụ, để sắp xếp một danh sách bằng một tài sản :

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

Xin lỗi nó chắc chắn là tham số x là null, không phải thuộc tính của nó. Tôi không muốn điều này là không an toàn - nó không được rỗng. – cbp

0

Câu trả lời của Marc rất hữu ích. Tôi đồng ý với anh ta rằng NullReference là do gọi CompareTo trên một tài sản null. Mà không cần một lớp mở rộng, bạn có thể làm:

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

nơi SomePropType là loại SomeProp

0

Đối với mục đích gỡ lỗi, bạn muốn phương pháp của bạn để được null-an toàn. (hoặc ít nhất, bắt ngoại lệ null-ref., và xử lý nó theo một số cách mã hóa cứng). Sau đó, sử dụng trình gỡ rối để xem những giá trị nào khác được so sánh, theo thứ tự nào và cuộc gọi nào thành công hoặc không thành công.

Sau đó, bạn sẽ tìm thấy câu trả lời của mình và sau đó bạn có thể xóa bỏ sự an toàn không có.

0

Bạn có thể chạy mã này không ...

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

Tôi tình cờ gặp vấn đề này và thấy rằng nó liên quan đến thuộc tính NaN trong thông tin nhập của tôi. Dưới đây là một trường hợp thử nghiệm tối thiểu mà nên tạo ra ngoại lệ:

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

Tôi cũng đã đi qua vấn đề này (tham chiếu null được thông qua để thực hiện IComparer tùy chỉnh của tôi) và cuối cùng phát hiện ra rằng vấn đề là do sử dụng chức năng so sánh không phù hợp.

Đây là bước đầu thực hiện IComparer tôi:

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

Sai lầm trong mã này được rằng Hãy so sánh sẽ trở về -1 bất cứ khi nào một trong những giá trị không thể phân tích đúng cách (trong trường hợp của tôi đó là do sai định dạng biểu diễn chuỗi các giá trị số nên TryParse luôn thất bại).

Lưu ý rằng trong trường hợp cả x và y được định dạng không chính xác (và do đó TryParse thất bại trên cả hai), gọi So sánh (x, y) và So sánh (y, x) sẽ mang lại kết quả tương tự: -1. Điều này tôi nghĩ là vấn đề chính. Khi gỡ lỗi, Compare() sẽ được chuyển qua con trỏ chuỗi rỗng như là một trong các đối số của nó tại một số điểm mặc dù bộ sưu tập đang được sắp xếp không đóng một chuỗi rỗng.

Ngay sau khi tôi đã khắc phục sự cố TryParse và đảm bảo tính nhất quán của việc triển khai của tôi, sự cố đã biến mất và So sánh không được truyền con trỏ null nữa.

Các vấn đề liên quan