سؤال

I'm trying to sort an array of objects with IComparer.

I wrote the code but it works only with the particular object. e.g.:

for this class

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;
    }
}

My code looks like:

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));
        }
    }
}   

But if I add another class with different properties it will be useless.

So is there any chance to modify this code so that it worked for objects with different properties?

هل كانت مفيدة؟

المحلول 3

Use an interface and use generic IComparer Interface instead of 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;}
     ...
}

نصائح أخرى

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;
      }
   }
}

Calling code:

Assuming a list named lst:

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

You may leverage the Create method of Comparer<T> which takes a Comparison delegate and returns Comparer<T>.

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

and for another type

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

If you're in prior to .Net4.5 you can use the following CreateComparer method.

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);
    }
}

Here is another take based on terrybozzio's.

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);
        }
    }
}

The cleanest way is to define an interface that both object implement, then use that in the comparison. Otherwise you're going to have a mess of case statements depending on the possible combination of objects:

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
        }
    }
}

Another approach would be using the generic IComparer interface and lambda expressions.

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));
    }
}

This class compares the Car's property passed in parameter in the constructor.

// Sort the cars by name
var nameCarComparer = new CarComparer<string>(car => car.Name);
Array.Sort(myArray, nameCarComparer);
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top