【问题标题】:How to compare and sort subclasses?如何比较和排序子类?
【发布时间】:2015-08-09 20:58:30
【问题描述】:

例如我有这些类和子类。

class Program
{
    static void Main(string[] args)
    {
        var animals = new List<Animal> { new Wolf {kills = 5}, 
                                         new Rabbit {name = "Uncle John"}, 
                                         new Eagle {eyeCount = 1}, 
                                         new Wolf {kills = 100}, 
                                         new Rabbit { name = "Human" } };
        animals.Sort();
//Suposted to be: Rabbit(Human), Rabbit(Uncle John), Wolf(5), Wolf(100), Eagle(1)
    }
}

enum SortOrder { Rabbit, Wolf, Eagle }

abstract class Animal{}
class Wolf : Animal
{
    public int kills = 0; //Marked public for simple initialization
}
class Rabbit : Animal
{
    public string name = "Funny Little Guy"; //Marked public for simple initialization
}
class Eagle : Animal
{
    public byte eyeCount = 2; //Marked public for simple initialization
}

我想对动物列表进行排序。 1) 在相同类型的其他对象中排序(按杀戮排序 Wolves,按名称排序兔子等)2) 对“SortOrder”中的子类组进行排序,因此 Rabbits 在列表中排在第一位,Eagles 在列表中排在最后。

我曾尝试通过实现 IComparable&lt;Animal&gt;, IComparable&lt;Wolf&gt;, IComparable&lt;Rabbit&gt;, IComparable&lt;Eagle&gt; 接口来实现这一点,但这让我无处可去,因为我无法完成这项工作,即使我可以,再添加 1 个子类会导致大量代码工作。

这是我的尝试:

abstract class Animal : IComparable<Animal>, IComparable<Wolf>, IComparable<Rabbit>, IComparable<Eagle>
{
    public abstract int CompareTo(Animal other);
    public abstract int CompareTo(Wolf other);
    public abstract int CompareTo(Rabbit other);
    public abstract int CompareTo(Eagle other);
}

class Wolf : Animal
{
    public int kills = 0; //Marked public for simple initialization

    public override int CompareTo(Animal other) => other.CompareTo(this);
    public override int CompareTo(Wolf other) => kills.CompareTo(kills);
    public override int CompareTo(Rabbit other) => SortOrder.Wolf.CompareTo(SortOrder.Rabbit);
    public override int CompareTo(Eagle other) => SortOrder.Wolf.CompareTo(SortOrder.Eagle);
}

但是通过这种方式我得到了相反的顺序,正如我所说的那样很难添加新的子类。

那么进行这种比较的有效方法是什么?

【问题讨论】:

  • 分享你尝试的代码(The IComparable's)

标签: c# sorting compare subclass


【解决方案1】:

为了尽量减少添加新代码并强制新类添加到层次结构中,我会做这样的设计:

首先,我将为每种具体的动物类型实现 IComparable。这很简单。其次,我会将 SortOrder OrderType 属性添加到抽象类并在每个具体实例中实现它。这将迫使扩展类的人重新评估枚举并可能为其添加新值。然后,主比较函数将首先检查此属性。如果不相等,则返回类型之间的比较。如果相等,只需在两个实例上调用 compare,因为可以安全地假设它们是相同的类型。

在这里实现。我实际上不得不使用反射,因为没有其他方法可以调用具体的比较器。但它似乎工作得很好,只要实际类型对应于返回的枚举。

enum SortOrder { Rabbit, Wolf, Eagle }

abstract class Animal : IComparable<Animal>
{
    public abstract SortOrder OrderType { get; }

    public int CompareGeneric(Animal x, Animal y)
    {
        // use reflection to call comparer on concrete animal type
        var comparerType = typeof(IComparable<>).MakeGenericType(x.GetType());
        var compareMethod = comparerType.GetMethod("CompareTo");

        return (int)compareMethod.Invoke(x, new object[] { y });
    }

    public int Compare(Animal x, Animal y)
    {
        // clever hack to compare the enums
        var diff = x.OrderType - y.OrderType;
        if (diff != 0)
            return diff;

        return CompareGeneric(x, y);
    }

    public int CompareTo(Animal other)
    {
        return Compare(this, other);
    }
}

class Wolf : Animal, IComparable<Wolf>
{
    public override SortOrder OrderType { get { return SortOrder.Wolf; } }

    public int kills = 0; //Marked public for simple initialization

    public int CompareTo(Wolf other)
    {
        return this.kills.CompareTo(other.kills);
    }
}
class Rabbit : Animal, IComparable<Rabbit>
{
    public override SortOrder OrderType { get { return SortOrder.Rabbit; } }

    public string name = "Funny Little Guy"; //Marked public for simple initialization

    public int CompareTo(Rabbit other)
    {
        return this.name.CompareTo(other.name);
    }
}
class Eagle : Animal, IComparable<Eagle>
{
    public override SortOrder OrderType { get { return SortOrder.Eagle; } }

    public byte eyeCount = 2; //Marked public for simple initialization

    public int CompareTo(Eagle other)
    {
        return this.eyeCount.CompareTo(other.eyeCount);
    }
}

【讨论】:

    【解决方案2】:

    我认为您可以使用 Animal 类实现 IComparable 并使用 Euphoric 提到的只读 SortOrder 枚举属性。 然后在每个类中你可以重写 CompareTo 方法来比较每个物种和它们的种类。

        public abstract class Animal : IComparable
        {
            public abstract SortOrder SortOrder { get; }
    
            public virtual int CompareTo(object obj)
            {
                Animal rightValue = (Animal)obj;
                return this.SortOrder < rightValue.SortOrder ? -1
                    : this.SortOrder > rightValue.SortOrder ? 1 : 0;
            }
        }
        public class Wolf : Animal
        {
            public override SortOrder SortOrder { get { return SortOrder.Wolf; } }
            public int kills = 0; //Marked public for simple initialization
            public override int CompareTo(object obj)
            {
                if (obj is Wolf)
                {
                    Wolf rightValue = (Wolf)obj;
                    return this.kills < rightValue.kills ? -1
                        : this.kills > rightValue.kills ? 1 : 0;
                }
                else
                {
                    return base.CompareTo(obj);
                }
            }
        }
        public class Rabbit : Animal
        {
            public override SortOrder SortOrder { get { return SortOrder.Rabbit; } }
            public string name = "Funny Little Guy"; //Marked public for simple initialization
    
            public override int CompareTo(object obj)
            {
                if (obj is Rabbit)
                {
                    Rabbit rightValue = (Rabbit)obj;
                    return String.Compare(this.name, rightValue.name);
                }
                else
                {
                    return base.CompareTo(obj);
                }
            }
        }
        public class Eagle : Animal
        {
            public override SortOrder SortOrder { get { return SortOrder.Eagle; } }
            public byte eyeCount = 2; //Marked public for simple initialization
            public override int CompareTo(object obj)
            {
                if (obj is Eagle)
                {
                    Eagle rightValue = (Eagle)obj;
                    return this.eyeCount < rightValue.eyeCount ? -1
                        : this.eyeCount > rightValue.eyeCount ? 1 : 0;
                }
                else
                {
                    return base.CompareTo(obj);
                }
            }
        }
    

    【讨论】:

      【解决方案3】:

      我会在你的 Animal 类中添加一个 SortKey 和 ICompareable 接口,并在派生类中实现逻辑。

          abstract class Animal : IComparable<Animal>
          {
              public abstract int SortKey { get; }
      
              public abstract int CompareTo(Animal other);
          }
          class Wolf : Animal
          {
              public int kills = 0; //Marked public for simple initialization
      
              public override int SortKey { get { return 100; } }
              public override int CompareTo(Animal other)
              {
                  if (other.SortKey != SortKey)
                  {
                      return SortKey.CompareTo(other.SortKey);
                  }
      
                  var otherWolf = other as Wolf;
      
                  if (otherWolf == null)
                  {
                      return -1;
                  }
      
                  return kills.compareTo(otherWolf.kills);
              }
          }
      

      【讨论】:

      • 我看到这个实现的问题是你必须在每个具体类中进行键比较。
      【解决方案4】:

      考虑使用IComparer across 类型;并实现 IComparable 以在特定类型进行排序。 Comparer 只会调用 Comparable 进行相同类型的比较。

      优势:

      • 没有一种类型需要知道它是类型之间的“相对”排序键。此外,基本类型不会被附加字段“弄脏”。 (虽然没有什么可以阻止将排序放在这样的“SortKey”字段中。)

      • 排序动物类型的规则被隔离在一个位置,即比较器。

        动物类型中的排序规则已经 由 IComparable 处理并且可以被覆盖/专门化 每种类型。

      缺点:

      • 收集/排序操作需要使用Comparer

      • Comparer 添加了“隐藏”逻辑,可能需要针对其他类型进行更新。 (仅当在基本类型中使用类似“SortKey”的方法时才需要这样做。)

      使用单独的 IComparaer/IComparable 方法是使用“SortKey”字段的更通用形式,将额外的排序混合到 IComparable 中,因为这样的字段可以被这样的 IComparer 使用实施。

      【讨论】:

      • 缺点:添加新的混凝土动物并不清楚将其添加到动物比较器中。我认为它可能会破坏 LSP。
      • 任何在遇到新的派生类型时导致代码中断,而不改变其余代码的行为,都违反了 LSP。
      【解决方案5】:

      我会用 Linq 解决你的问题

      using System.Collections.Generic;
      using System.Linq;
      
          public enum SortOrder
          {
              Rabbit,
              Wolf,
              Eagle
          }
      
          public class Program
          {
              public static void Main(string[] args)
              {
                  var animals = (new List<Animal> { new Wolf {Kills = 5},
                                           new Rabbit {Name = "Uncle John"},
                                           new Eagle {EyeCount = 1},
                                           new Wolf {Kills = 100},
                                           new Rabbit { Name = "Human" } })
                                  .OrderBy(x => x.SortOrder)
                                  .ThenBy(x => x.Classifier);
      
                  //Suposted to be: Rabbit(Human), Rabbit(Uncle John), Wolf(5), Wolf(100), Eagle(1)
              }
          }
      
          public abstract class Animal
          {
              public abstract object Classifier { get; }
      
              public string Name { get; set; }
      
              public SortOrder SortOrder { get; set; }
          }
      
          public class Eagle : Animal
          {
              public Eagle()
              {
                  this.SortOrder = SortOrder.Eagle;
              }
      
              public override object Classifier
              {
                  get { return this.EyeCount; }
              }
      
              public byte EyeCount { get; set; }
          }
      
          public class Rabbit : Animal
          {
              public Rabbit()
              {
                  this.Name = "Funny Little Guy";
                  this.SortOrder = SortOrder.Rabbit;
              }
      
              public override object Classifier
              {
                  get { return this.Name; }
              }
          }
      
          public class Wolf : Animal
          {
              public Wolf()
              {
                  this.Name = "Funny Little Guy";
                  this.SortOrder = SortOrder.Wolf;
              }
      
              public override object Classifier
              {
                  get { return this.Kills; }
              }
      
              public int Kills { get; set; }
          }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-01-05
        • 2010-11-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-03-20
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多