Question

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?

Était-ce utile?

La solution 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;}
     ...
}

Autres conseils

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);
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top