Reputation: 31
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?
Upvotes: 2
Views: 7197
Reputation: 3135
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);
}
}
}
Upvotes: 1
Reputation: 7235
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);
Upvotes: 0
Reputation: 73442
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);
}
}
Upvotes: 4
Reputation: 4542
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));
Upvotes: 4
Reputation: 18958
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;}
...
}
Upvotes: 3
Reputation: 152491
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
}
}
}
Upvotes: 0