通用 OrderBy() 调用中缺少 IComparable 接口的编译时检测

Compile-time detection of missing IComparable interface in generic OrderBy() call

当列表中的对象未实现 IComparable 接口时,通用方法 OrderBy() 或 ThenBy() 仅在 运行 时失败。就像这个例子:

public class DataClass
{
    public int Value { get; set; }
    public DataClass(int value)
    {
        Value = value;
    }
}

void Test()
{
    var list = new List<DataClass>() { new DataClass(10), new DataClass(1), new DataClass(5) };
    var sortedList = list.OrderBy(data => data).ToList(); // InvalidOperationException thrown by OrderBy()
}

有没有办法在编译时检测到这个问题?

通过检测问题我的意思是编译器向我显示 OrderBy() 调用中使用的 lambda 函数没有 return 实现 IComparable 接口的对象的错误。我想查看编译时错误,而不是 运行 次错误。

我在此处寻找按易于实施排序的最便宜的修复程序:

  1. 打开一些编译器标志,警告我传递给 OrderBy() 的对象没有必需的接口。
  2. 使用某种可以检测缺失接口的代码分析器。
  3. 如果#1 和#2 不可行,则将 LINQ 扩展方法替换为现有的相当流行的 Nuget 包中的方法。
  4. 最后一个选项是将未明确定义所需接口的 LINQ 方法替换为明确定义所需接口的方法。

当我回答这个问题时,我想我可能已经找到了这个约束不存在的原因:keySelectorKey 的类型可以实现 或者 IComaprable<TKey> IComparable,而且我没有办法在约束中使用 OR!

因此,鉴于这些信息,如果您想编写自己的扩展方法来添加类型约束,您要么必须为每个重载编写两个(具有不同的名称),要么您只需要必须选择将 TKey 类型约束到哪个接口。

如果可以接受,请继续阅读我的原始答案...


我认为此方法出现 compile-time 错误的唯一方法是将其包装在您自己的扩展方法中,在调用 Linq 之前添加 IComparable<T> 约束方法:

public static class Extensions
{
    public static IEnumerable<TSource> OrderByConstrained<TSource, TKey>(
        this IEnumerable<TSource> source, Func<TSource, TKey> keySelector) 
        where TKey : IComparable<TKey>
    {
        return source.OrderBy(keySelector);
    }
}

现在你会得到一个编译时错误,直到 Data class 实现 IComparable<DataClass>:

var sortedList = list.OrderByConstrained(data => data);

// Compile Error CS0311: The type 'Test.Program.DataClass' cannot be used as type 
// parameter 'TKey' in the generic type or method 
// 'Extensions.OrderByConstrained<TSource, TKey>
// (IEnumerable<TSource>, Func<TSource, TKey>)'. 
// There is no implicit reference conversion from 'Test.Program.DataClass' to 
// 'System.IComparable<Test.Program.DataClass>'.

然后实现接口将解决编译错误:

public class DataClass : IComparable<DataClass>
{
    public int Value { get; set; }

    public DataClass(int value)
    {
        Value = value;
    }

    public int CompareTo(DataClass other)
    {
        return other == null ? 1 : Value.CompareTo(other.Value);
    }
}