【问题标题】:Generic IComparer for sorting different objects in different properties通用 IComparer 用于对不同属性中的不同对象进行排序
【发布时间】:2013-12-03 04:52:42
【问题描述】:

我正在尝试使用 IComparer 对一组对象进行排序。

我编写了代码,但它只适用于特定对象。例如:

对于这个类

public class Cars
{
    public string Name { get; set; }
    public string Manufacturer { get; set; }
    public int Year { get; set; }

    public Cars(string name, string manufacturer, int year)
    {
        Name = name;
        Manufacturer = manufacturer;
        Year = year;
    }
}

我的代码如下:

class MySort 
{
    public class SortByYears : IComparer
    {
        int IComparer.Compare(Object x, Object y)
        {
            Cars X = (Cars)x, Y = (Cars)y;                
            return (X.Year.CompareTo(Y.Year));
        }
    }

    public class SortByName : IComparer
    {
        int IComparer.Compare(Object x, object y)
        {
            Cars X = (Cars)x, Y = (Cars)y;
            return (X.Name.CompareTo(Y.Name));
        }
    }

    public class SortByManyfacturer : IComparer
    {
        int IComparer.Compare(object x, object y)
        {
            Cars X = (Cars)x, Y = (Cars)y;
            return (X.Manufacturer.CompareTo(Y.Manufacturer));
        }
    }
}   

但如果我添加另一个具有不同属性的类,它将毫无用处。

那么有没有机会修改这段代码,使其适用于具有不同属性的对象?

【问题讨论】:

  • 我不确定我是否理解你的问题。因此,您可能还想对 Cat 类使用 SortByName,但不想再次为 Cat 实现它,对吧?您需要一个包含属性名称的 Car 和 Cat 基类,然后将您的比较器更改为:
  • 公共类 SortByName : IComparer { int IComparer.Compare(Object x, object y) { Base X = (Base)x, Y = (Base)y;返回(X.Name.CompareTo(Y.Name)); } }
  • 那么您可以按名称比较 2 只猫、按名称比较 2 辆汽车以及按名称比较猫和汽车。
  • 好吧,在这种情况下,我需要一个额外的课程。我想在没有它的情况下这样做。还是谢谢。
  • 编辑你的问题更清楚!

标签: c# arrays sorting icomparable icomparer


【解决方案1】:
class SortComparer<T> : IComparer<T>
{
   private PropertyDescriptor PropDesc = null;
   private ListSortDirection Direction =
      ListSortDirection.Ascending;

   public SortComparer(object item,string property,ListSortDirection direction)
   {
       PropDesc = TypeDescriptor.GetProperties(item)[property];
       Direction = direction;
   }

   int IComparer<T>.Compare(T x, T y)
   {    
      object xValue = PropDesc.GetValue(x);
      object yValue = PropDesc.GetValue(y);
      return CompareValues(xValue, yValue, Direction);
   }

   private int CompareValues(object xValue, object yValue,ListSortDirection direction)
   {

      int retValue = 0;
      if (xValue is IComparable) // Can ask the x value
      {
         retValue = ((IComparable)xValue).CompareTo(yValue);
      }
      else if (yValue is IComparable) //Can ask the y value
      {
         retValue = ((IComparable)yValue).CompareTo(xValue);
      }
      // not comparable, compare String representations
      else if (!xValue.Equals(yValue))
      {
         retValue = xValue.ToString().CompareTo(yValue.ToString());
      }
      if (direction == ListSortDirection.Ascending)
      {
         return retValue;
      }
      else
      {
         return retValue * -1;
      }
   }
}

调用代码:

假设一个名为 lst 的列表:

lst.Sort(new SortComparer<Cars>(lst[0],"YourPropertyName",ListSortDirection.Ascending));

【讨论】:

  • 如果你必须使用is然后cast最好使用as
  • 我猜有太多未知的语法。不过我会试着分析一下,谢谢帮助。
【解决方案2】:

您可以利用Comparer&lt;T&gt;Create 方法,该方法接受Comparison 委托并返回Comparer&lt;T&gt;

var carnameComparer = Comparer<Cars>.Create((x, y) => x.Year.CompareTo(y.Year));
var carManufacturerComparer = Comparer<Cars>.Create((x, y) => x.Manufacturer.CompareTo(y.Manufacturer));

对于另一种类型

var carsComparer = Comparer<SomeType>.Create((x, y) => x.SomeProperty.CompareTo(y.SomeProperty));

如果你在.Net4.5之前你可以使用下面的CreateComparer方法。

private static IComparer<T> CreateComparer<T>(Comparison<T> comparison)
{
    return new ComparisonComparer<T>(comparison);
}

public class ComparisonComparer<T> : IComparer<T>
{
    private Comparison<T> comparison;
    public ComparisonComparer(Comparison<T> comparison)
    {
        if (comparison == null)
        {
            throw new ArgumentNullException("comparison");
        }
        this.comparison = comparison;
    }

    public int Compare(T x, T y)
    {
        return comparison(x, y);
    }
}

【讨论】:

  • @JuliánUrbano 更新了我的答案
【解决方案3】:

使用接口并使用泛型IComparer Interface 而不是IComparer

public interface IObjectWithNameProperty
{
    string Name {get; set;}
}

public class MyNameComparer : IComparer<IObjectWithNameProperty>
{
    public int Compare(IObjectWithNameProperty x, IObjectWithNameProperty y)
    {
        ...
    }
}

public class Car: IObjectWithNameProperty
{
     public string Name  {get;set;}
     ...
}
public class Dog: IObjectWithNameProperty
{
     public string Name  {get;set;}
     ...
}

【讨论】:

  • 我认为这就是我所需要的。如果我让您感到困惑,谢谢和抱歉。
  • @Mikho :) 没问题!
【解决方案4】:

这是另一个基于 terrybozzio 的拍摄。

public class PropertyComparer<T> : IComparer<T> where T : new()
{
    private PropertyDescriptor PropDesc = null;
    private ListSortDirection Direction = ListSortDirection.Ascending;

    public PropertyComparer(string property, ListSortDirection direction)
    {
        T item = new T();
        PropDesc = TypeDescriptor.GetProperties(item)[property];
        Direction = direction;


        Type interfaceType = PropDesc.PropertyType.GetInterface("IComparable");

        if (interfaceType == null && PropDesc.PropertyType.IsValueType)
        {
            Type underlyingType = Nullable.GetUnderlyingType(PropDesc.PropertyType);

            if (underlyingType != null)
            {
                interfaceType = underlyingType.GetInterface("IComparable");
            }
        }

        if (interfaceType == null)
        {
            throw new NotSupportedException("Cannot sort by " + PropDesc.Name +
                ". This" + PropDesc.PropertyType.ToString() +
                " does not implement IComparable");
        }
    }

    int IComparer<T>.Compare(T x, T y)
    {
        object xValue = PropDesc.GetValue(x);
        object yValue = PropDesc.GetValue(y);
        IComparable comparer = (IComparable)xValue;
        if (Direction == ListSortDirection.Ascending)
        {
            return comparer.CompareTo(yValue);
        }
        else
        {
            return -1 * comparer.CompareTo(yValue);
        }
    }
}

【讨论】:

    【解决方案5】:

    最干净的方法是定义一个两个对象都实现的接口,然后在比较中使用它。否则,您将有一堆案例陈述,具体取决于 可能的对象组合:

    public class SortByYears : IComparer
    {
        int IComparer.Compare(Object x, Object y)
        {
            if(x is Cars)
            {
                Cars X = (Cars)x
                if(y is Cars)
                {
                    Y = (OtherCars)y;                
                    return (X.Year.CompareTo(Y.Year));
    
                if(y is OtherCars)
                {
                    Y = (OtherCars)y;                
                    return (X.Year.CompareTo(Y.Year));
                }
            }
            if(x is OtherCars)
            {
                 ... repeat upper block
            }
        }
    }
    

    【讨论】:

    • 你最好只创建 N 个不同的比较器,而不是检查它们内部的类型。它增加了很少的额外工作并增加了类型安全的潜力。
    • @JuliánUrbano 完全正确。
    【解决方案6】:

    另一种方法是使用通用 IComparer interface 和 lambda 表达式。

    class CarComparer<T> : IComparer<Car> where T : IComparable<T>
    {
        private readonly Func<Car, T> _sortExpression;
    
        public CarComparer(Func<Car, T> sortExpression)
        {
            _sortExpression = sortExpression;
        }
    
        public int Compare(Car x, Car y)
        {
            return _sortExpression(x).CompareTo(_sortExpression(y));
        }
    }
    

    这个类比较了在构造函数中传入参数的 Car 的属性。

    // Sort the cars by name
    var nameCarComparer = new CarComparer<string>(car => car.Name);
    Array.Sort(myArray, nameCarComparer);
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-10-02
      • 2012-11-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多