为什么C#让我在不知道如何排序时编译排序代码

时间:2009-03-11 06:11:31

标签: c# sorting

我觉得奇怪的是C#让我在我的类上调用sort而没有指定一种方法来对它们进行排序,也没有写出比较重载。当我运行此代码时,会弹出此错误

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.

为什么C#让我在不知道如何排序时编译这段代码! -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时,它将编译并运行。

4 个答案:

答案 0 :(得分:13)

对List&lt; T&gt;的通用参数没有约束。要求它实现IComparable&lt; T&gt;。如果有,它会(某种程度上)保证元素可以被排序,但是你将无法使用List&lt; T&gt;保留任何未实现IComparable的内容。由于您可能不会对您创建的每个列表进行排序,因此这是正确的决定。

答案 1 :(得分:2)

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

它允许编译,因为这不是语言功能,它是一个框架功能。

答案 2 :(得分:2)

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

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

答案 3 :(得分:0)

C#可能只是在System.Object中有一些概念,关于你如何订购对象,就像它使用Equals来比较它们的身份一样。

不幸的是,这导致了对内涵性与扩展性,本地化等的担忧。

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

因此,没有好的方法可以在编译时查看类型,并明确知道它是否具有有意义的排序。 =(

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

通过允许多个IComparerIComparer<T> s,您可以拥有多个替代排序的概念,这些排序可以在同一类型上运行,所以这很好,但一切都不顺利。

在内部,c#使用一系列规则来查找Comparer<T>.Default,如果T是IComparable<T>的实例或Nullable<T>的形式,则它会以不同的方式处理。

e.g。来自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>();
    }

从本质上讲,这可以让Microsoft逐步为自己的代码添加特殊情况,但遗憾的是,仅使用Comparer<T>.Default进行自定义IComparable<T>实例的代码非常糟糕。

IList<T>上的Sort方法假定Comparer<T>.Default会提供可用于比较对象的内容,但它无法查看类型T并告诉它,所以它假设它是安全的,并且只在运行时稍后才意识到MyClass没有在播放。