【问题标题】:Sorting based on multiple fields using IComparer [closed]使用 IComparer 基于多个字段进行排序 [关闭]
【发布时间】:2021-12-26 20:42:06
【问题描述】:

我使用下面的代码对List<DataAccessViewModel> 列表进行排序。

这是排序顺序:

  1. PriorityScore
  2. MName
  3. CName
  4. FName

它按预期工作。

public int Compare(DataAccessViewModel x, DataAccessViewModel y)
{
    if (x == null || y == null)
    {
        return 0;
    }

    return x.CompareTo(y);
}

public int CompareTo(DataAccessViewModel mod)
{
    int retval = (int)(this.PriorityScore?.CompareTo(mod.PriorityScore));
    if(retval != 0)
        return retval;
    else
    {
        retval = (this.MName ?? "zzzzzzzzzzzzz").CompareTo(mod.MName ?? "zzzzzzzzzzzzz");
        if (retval != 0)
            return retval;
        else
        {
            retval = (this.CName ?? "zzzzzzzzzzzzz").CompareTo(this.CName ?? "zzzzzzzzzzzzz");
            if (retval != 0)
                return retval;
            else
                retval = (this.FName ?? "zzzzzzzzzzzzz").CompareTo(this.FName ?? "zzzzzzzzzzzzz");
        }
    }
        
    return retval;
}

但代码对我来说看起来很笨拙。有没有更好的方法或者是这样吗?

【问题讨论】:

    标签: c# sorting model-view-controller icomparer


    【解决方案1】:

    当前代码中有一个问题

    public int Compare(DataAccessViewModel x, DataAccessViewModel y) 
    {
        if (x == null || y == null)
        {
            return 0;
        }
        ...
    

    由于null 等于任何值,那么所有值都相等

       a == null, null == b => a == b
    

    这不是您要实施的规则。我建议这样的事情

    using System.Linq;
    
    ...
    
    // static: we don't want "this"
    public static int TheCompare(DataAccessViewModel x, DataAccessViewModel y) {
      // Special cases, nulls
      if (ReferenceEquals(x, y)) // if references are shared, then equal
        return 0;
      if (null == x) // let null be smaller than any other value
        return -1;
      if (null == y)
        return 1;
     
      // How we compare strings 
      static int MyCompare(string left, string right) =>
          ReferenceEquals(left, right) ? 0 
        : null == left ? 1    // null is greater than any other string
        : null == right ? -1
        : string.Compare(left, right);
    
      // Func<int> for lazy computation
      return new Func<int>[] {
          () => x.PriorityScore?.CompareTo(y.PriorityScore) ?? -1,
          () => MyCompare(x.MName, y.MName),
          () => MyCompare(x.CName, y.CName),  
          () => MyCompare(x.FName, y.FName), 
        }
        .Select(func => func())
        .FirstOrDefault(value => value != 0);
    }
    

    然后对于界面我们有

    public int CompareTo(DataAccessViewModel other) => TheCompare(this, other);
    
    // I doubt if you want to implement both IComparable<T> and
    // IComparer<T> but you can easily do it
    public int Compare(DataAccessViewModel x, DataAccessViewModel y) =>
      TheCompare(x, y);
    

    【讨论】:

    • 不能使用static 来实现 IComparer 接口。
    • @StriplingWarrior:我怀疑在 *same 类中实现 both*` IComparable` 和 IComparer&lt;T&gt; 接口是否合理。但是,我已经编辑了答案以显示如何完成此实现。
    【解决方案2】:

    你快到了:只需跳过return 后面的else 语句:

    public int CompareTo(DataAccessViewModel mod)
    {
        // I worry about this line -- if you're sure that PriorityScore is not null,
        // why not use this.PriorityScore.Value to make that clear?
        int retval = (int)(this.PriorityScore?.CompareTo(mod.PriorityScore));
        if (retval != 0)
            return retval;
    
        retval = (this.MName ?? "zzzzzzzzzzzzz").CompareTo(mod.MName ?? "zzzzzzzzzzzzz");
        if (retval != 0)
            return retval;
    
        retval = (this.CName ?? "zzzzzzzzzzzzz").CompareTo(this.CName ?? "zzzzzzzzzzzzz");
        if (retval != 0)
            return retval;
    
        return (this.FName ?? "zzzzzzzzzzzzz").CompareTo(this.FName ?? "zzzzzzzzzzzzz");
    }
    

    您还可以使用Comparer&lt;T&gt;.Default 正确处理可空值。这会将null 排序为等于其他null 值,但小于任何其他对象。

    public int CompareTo(DataAccessViewModel mod)
    {
        int retval = Comparer<int?>.Default.Compare(this.PriorityScore, mod.PriorityScore);
        if (retval != 0)
            return retval;
    
        retval = Comparer<string>.Default.Compare(this.MName, mod.MName);
        if (retval != 0)
            return retval;
    
        retval = Comparer<string>.Default.Compare(this.CName, mod.CName);
        if (retval != 0)
            return retval;
    
        return Comparer<string>.Default.Compare(this.FName, mod.FName);
    }
    

    【讨论】:

      【解决方案3】:

      考虑利用default behavior of ValueTuples

      IComparer(这应该和你的类不一样)

      public int Compare(DataAccessViewModel x, DataAccessViewModel y)
      {
          if (x == null && y == null)
          {
              return 0;
          }
      
          if (x == null) return -1;
          if (y == null) return 1;
      
          var thisCompareOrder = (x.PriorityScore, x.MName, x.CName, x.FName);
          var thatCompareOrder = (y.PriorityScore, y.MName, y.CName, y.FName);
          return thisCompareOrder.CompareTo(thatCompareOrder);
      }
      

      如果您希望您的类默认具有这些语义,请实现 IComparable。

      public int CompareTo(DataAccessViewModel mod)
      {
          if(mod == null) return 1;
      
          var thisCompareOrder = (this.PriorityScore, this.MName, this.CName, this.FName);
          var thatCompareOrder = (mod.PriorityScore, mod.MName, mod.CName, mod.FName);
          return thisCompareOrder.CompareTo(thatCompareOrder);
      }
      

      您也可以考虑将您的原始类设为record,默认情况下它具有基于值的语义。

      【讨论】:

        猜你喜欢
        • 2012-12-29
        • 2018-03-16
        • 1970-01-01
        • 1970-01-01
        • 2017-10-20
        • 1970-01-01
        • 2019-04-10
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多