English 中文(简体)
为什么C#让我编译排序代码,而它却不知道如何排序?
原标题:
  • 时间:2009-03-11 06:11:31
  •  标签:

我觉得很奇怪,C#让我调用类的排序功能,却不需要指定排序方式或编写比较重载函数。当我运行这段代码时,出现了错误。

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.

Why does C# let me compile this code when it doesnt know how to sort this! -edit-

Codex问为什么要这样做。我在我的评论中写了一份理论。这里是一些示例代码。

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

如果你在返回之前取消注释该行,你会得到一个编译时错误。当你在类c中取消注释IComparable时,它将编译并工作。

最佳回答

List 的通用参数不受需要实现 IComparable 约束的限制。如果有的话,它会(在某种程度上)保证元素可以排序,但您将无法使用 List 来保存未实现 IComparable 的任何内容。由于您可能不会对创建的每个列表进行排序,因此这是正确的决定。

问题回答

排序应检查您的对象是否实现了IComparable。这是一个运行时检查,由于您可能没有实现它,所以默认的比较器不知道如何处理您的对象,因此会抛出异常。

它可以编译是因为这不是一种语言特性,而是一个框架特性。

仅供参考;本质上,它使用Comparer<T>.Default来进行比较。它实现了IComparer<T>,并可以比较两个T类型的对象。实际实现是在第一次请求时选择的(每个T);框架使用一些模式来选择实现-例如,类,“正常”的结构和Nullable<T>都是单独处理的。同样,它根据T是否实现了IComparable<T>IComparable或二者都不是(在这种情况下,它会抛出异常)来做出选择。

这提供了一种非常简单的方法来进行“鸭子类型”排序。同样,也有一个EqualityComparer<T>. Default,它检查IEquatable<T>,否则默认为object.Equals

C#可以在System.Object中添加一些内置的概念,比如它使用Equals来比较对象的标识一样,来对对象进行排序。

不幸的是,这会引起有关意向性与外延性、本土化等方面的担忧。

有一个 IComparable<T> 接口,但内置的值类型无法实现这样的接口。

因此,在编译时仅查看类型并确切地知道它是否具有有意义的排序的好方法不存在。 =(

在C#中演进的机制是使用由Comparer<T>.Default返回的IComparer<T>实例,并在尝试对缺乏排序的内容进行排序时获得运行时错误。

通过允许多个IComparerIComparer<T>,可以在同一类型上使用多种替代排序方法,这一点非常好,但并不完美。

在内部,C#使用一堆规则来查找Comparer<T>.Default,如果T是IComparable<T>的实例或Nullable<T>等形式,则会 differently 处理。

例如: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>();
    }

本质上,这使得微软逐渐添加其自己代码的特殊情况,但不幸的是,仅使用Comparer<T>.Default作为自定义IComparable<T>实例的代码结果非常糟糕。

IList<T> 上的 Sort 方法假设 Comparer<T>.Default 会找出可用于比较对象的内容,但它无法查看类型 T 并判断它是否可用,因此它假设它是安全的,并仅在运行时才意识到 MyClass 没有遵守。





相关问题
热门标签