默认比较器在 C# 中如何工作?
How does the default comparator work in C#?
我正在使用 OrderBy 进行一些基于属性的排序,我找到了 documentation for the default comparer,但它并没有向我解释太多。如果对象没有实现 System.IComparable<T>
,它如何生成 Comparer<T>
?
例如,我目前正在根据类型 object
的 属性 值对对象列表进行排序。它们是下面的数字类型,排序工作正常。 C#/Linq 如何知道如何对对象进行排序?它是否对基元进行了一些拆箱?它会做一些哈希检查吗?这将如何转化为大于或小于?
如果它们是更复杂的类型,这会因错误而失败,或者 OrderBy 什么都不做,或者它甚至会以一种毫无意义的方式排序吗?
在 Reference Source 上浏览时,它 returns 一个 ObjectComparer<T>
,它是一种特殊的内部类型,仅将工作委托给 System.Collections.Comparer.Default
。
反过来,如果它接收到未实现 IComparable
的参数,则会抛出异常。由于该比较器通过向下转换和反射工作,因此它不关心对象的静态类型是否未实现 IComparable
(如果您有一个 object
列表就是这种情况)。
所以底线是这样的:首先检查 IComparable<T>
,然后检查 IComparable
,最后抛出异常。
顺便说一句,大多数(我什至可以说全部)内置类型都以某种方式实现 IComparable<T>
,因此这就是它们的排序方式。
int
,或者更准确地说,Int32
确实实际实现了IComparable
,所以它起作用了。 (source)
OrderBy
似乎尝试将比较器用于它遇到的第一种类型,因此如果您从一个未实现 IComparable
的对象开始,您将得到一个 ArgumentException
:
At least one object must implement IComparable
如果您以 Int32
开头,那么您将得到与以下相同的异常:
Object must be of type Int32
来自 Int32
的比较器
查看内部结构,如果对象是通用的并实现 IComparable<T>
,则默认比较器将 return 一个 GenericComparer
实例,该实例将对象转换为该接口以执行比较。原始类型已经实现了它。可为空的原始类型也会自动实现该接口,因此 returned NullableComparer
的工作方式类似。在那些场景中没有boxing/unboxing。
否则,它将尝试将对象转换为非泛型 IComparable
实例,这可能会导致结构装箱或抛出 ArgumentException
如果类型未实现它。
你可以检查参考来源和see for yourself它的作用。
public static Comparer<T> Default {
get {
Contract.Ensures(Contract.Result<Comparer<T>>() != null);
Comparer<T> comparer = defaultComparer;
if (comparer == null) {
comparer = CreateComparer();
defaultComparer = comparer;
}
return comparer;
}
}
private static Comparer<T> CreateComparer() {
RuntimeType t = (RuntimeType)typeof(T);
// If T implements IComparable<T> return a GenericComparer<T>
#if FEATURE_LEGACYNETCF
//(SNITP)
#endif
if (typeof(IComparable<T>).IsAssignableFrom(t)) {
return (Comparer<T>)RuntimeTypeHandle.CreateInstanceForAnotherGenericParameter((RuntimeType)typeof(GenericComparer<int>), t);
}
// If T is a Nullable<U> where U implements IComparable<U> return a NullableComparer<U>
if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable<>)) {
RuntimeType u = (RuntimeType)t.GetGenericArguments()[0];
if (typeof(IComparable<>).MakeGenericType(u).IsAssignableFrom(u)) {
return (Comparer<T>)RuntimeTypeHandle.CreateInstanceForAnotherGenericParameter((RuntimeType)typeof(NullableComparer<int>), u);
}
}
// Otherwise return an ObjectComparer<T>
return new ObjectComparer<T>();
}
所以它所做的是检查类型是否实现了 IComparable<T>
,如果实现了则使用类型内置的比较器(数字类型的对象列表将遵循此分支)。如果类型是 Nullable<ICompareable<T>>
,它会再次进行相同的检查。如果这也失败了,它使用 ObjectComparer
,它使用 Comparer.Default
.
Here is the Compare code 对于 Comparer.Default
public int Compare(Object a, Object b) {
if (a == b) return 0;
if (a == null) return -1;
if (b == null) return 1;
if (m_compareInfo != null) {
String sa = a as String;
String sb = b as String;
if (sa != null && sb != null)
return m_compareInfo.Compare(sa, sb);
}
IComparable ia = a as IComparable;
if (ia != null)
return ia.CompareTo(b);
IComparable ib = b as IComparable;
if (ib != null)
return -ib.CompareTo(a);
throw new ArgumentException(Environment.GetResourceString("Argument_ImplementIComparable"));
}
如您所见,它会检查 a
或 b
是否实现了 IComparable
,如果两者都没有,则抛出异常。
我正在使用 OrderBy 进行一些基于属性的排序,我找到了 documentation for the default comparer,但它并没有向我解释太多。如果对象没有实现 System.IComparable<T>
,它如何生成 Comparer<T>
?
例如,我目前正在根据类型 object
的 属性 值对对象列表进行排序。它们是下面的数字类型,排序工作正常。 C#/Linq 如何知道如何对对象进行排序?它是否对基元进行了一些拆箱?它会做一些哈希检查吗?这将如何转化为大于或小于?
如果它们是更复杂的类型,这会因错误而失败,或者 OrderBy 什么都不做,或者它甚至会以一种毫无意义的方式排序吗?
在 Reference Source 上浏览时,它 returns 一个 ObjectComparer<T>
,它是一种特殊的内部类型,仅将工作委托给 System.Collections.Comparer.Default
。
反过来,如果它接收到未实现 IComparable
的参数,则会抛出异常。由于该比较器通过向下转换和反射工作,因此它不关心对象的静态类型是否未实现 IComparable
(如果您有一个 object
列表就是这种情况)。
所以底线是这样的:首先检查 IComparable<T>
,然后检查 IComparable
,最后抛出异常。
顺便说一句,大多数(我什至可以说全部)内置类型都以某种方式实现 IComparable<T>
,因此这就是它们的排序方式。
int
,或者更准确地说,Int32
确实实际实现了IComparable
,所以它起作用了。 (source)
OrderBy
似乎尝试将比较器用于它遇到的第一种类型,因此如果您从一个未实现 IComparable
的对象开始,您将得到一个 ArgumentException
:
At least one object must implement IComparable
如果您以 Int32
开头,那么您将得到与以下相同的异常:
Object must be of type
Int32
来自 Int32
查看内部结构,如果对象是通用的并实现 IComparable<T>
,则默认比较器将 return 一个 GenericComparer
实例,该实例将对象转换为该接口以执行比较。原始类型已经实现了它。可为空的原始类型也会自动实现该接口,因此 returned NullableComparer
的工作方式类似。在那些场景中没有boxing/unboxing。
否则,它将尝试将对象转换为非泛型 IComparable
实例,这可能会导致结构装箱或抛出 ArgumentException
如果类型未实现它。
你可以检查参考来源和see for yourself它的作用。
public static Comparer<T> Default {
get {
Contract.Ensures(Contract.Result<Comparer<T>>() != null);
Comparer<T> comparer = defaultComparer;
if (comparer == null) {
comparer = CreateComparer();
defaultComparer = comparer;
}
return comparer;
}
}
private static Comparer<T> CreateComparer() {
RuntimeType t = (RuntimeType)typeof(T);
// If T implements IComparable<T> return a GenericComparer<T>
#if FEATURE_LEGACYNETCF
//(SNITP)
#endif
if (typeof(IComparable<T>).IsAssignableFrom(t)) {
return (Comparer<T>)RuntimeTypeHandle.CreateInstanceForAnotherGenericParameter((RuntimeType)typeof(GenericComparer<int>), t);
}
// If T is a Nullable<U> where U implements IComparable<U> return a NullableComparer<U>
if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable<>)) {
RuntimeType u = (RuntimeType)t.GetGenericArguments()[0];
if (typeof(IComparable<>).MakeGenericType(u).IsAssignableFrom(u)) {
return (Comparer<T>)RuntimeTypeHandle.CreateInstanceForAnotherGenericParameter((RuntimeType)typeof(NullableComparer<int>), u);
}
}
// Otherwise return an ObjectComparer<T>
return new ObjectComparer<T>();
}
所以它所做的是检查类型是否实现了 IComparable<T>
,如果实现了则使用类型内置的比较器(数字类型的对象列表将遵循此分支)。如果类型是 Nullable<ICompareable<T>>
,它会再次进行相同的检查。如果这也失败了,它使用 ObjectComparer
,它使用 Comparer.Default
.
Here is the Compare code 对于 Comparer.Default
public int Compare(Object a, Object b) {
if (a == b) return 0;
if (a == null) return -1;
if (b == null) return 1;
if (m_compareInfo != null) {
String sa = a as String;
String sb = b as String;
if (sa != null && sb != null)
return m_compareInfo.Compare(sa, sb);
}
IComparable ia = a as IComparable;
if (ia != null)
return ia.CompareTo(b);
IComparable ib = b as IComparable;
if (ib != null)
return -ib.CompareTo(a);
throw new ArgumentException(Environment.GetResourceString("Argument_ImplementIComparable"));
}
如您所见,它会检查 a
或 b
是否实现了 IComparable
,如果两者都没有,则抛出异常。