How do I create a generic method which can take in a sortExpression based on MyObject properties, something like this :
void CreateSortedReport(IList<MyObject> list, Expression<Func<MyObject, TSort>> sortExpression, bool ascending = true)
{
//sort the [list] by sortExpression and by direction;
}
so that I can use it like this:
CreateSortedReport(myItems, x=>x.Name);
or
CreateSortedReport(myItems, x=>x.CreateDate);
Edit 1:
The reason I ask for generic method since there are some methods which are very similar:
CreateReportSortedByName(myItems) {
return myItems.OrderBy(x=>x.Name);
}
CreateReportSortedByDate(myItems) {
return myItems.OrderBy(x=>x.CreateDate);
}
Assuming MyObject is a concrete type, simply add a single generic parameter (<TSort>) to your method signature:
void CreateReport<TSort>(IList<MyObject> list, Expression<Func<MyObject, TSort>> sortExpression, bool ascending = true)
If you want MyObject to be a generic parameter, your signature will look like this:
void CreateReport<MyObject, TSort>(IList<MyObject> list, Expression<Func<MyObject, TSort>> sortExpression, bool ascending = true)
To use the inbuilt sort function to provide an in-place sort, you'd need to write a class that implements IComparer<T>.
For example:
class CompareWithDelegate<TOnObject, TSort> : IComparer<TOnObject>
{
Func<TOnObject, TSort> evaluator;
IComparer comparer = Comparer.Default;
bool ascending;
public CompareWithDelegate(Expression<Func<TOnObject, TSort>> expr, bool ascending = true)
{
evaluator = expr.Compile();
this.ascending = ascending;
}
public int Compare(TOnObject left, TOnObject right)
{
var leftVal = evaluator(left);
var rightVal = evaluator(right);
return (ascending ? 1 : -1) * comparer.Compare(leftVal, rightVal);
}
}
And then:
void CreateSortedReport<TSort>(List<MyObject> list, Expression<Func<MyObject, TSort>> sortExpression, bool ascending = true)
{
list.Sort(new CompareWithDelegate<MyObject, TSort>(sortExpression));
list.Dump();
}
Note that the list must be List<MyObject>, not IList<MyObject>.
Alternatively, if you don't need it to be in-place, you have two options:
Change the signature to Func<MyObject, TSort>:
void CreateSortedReport<TSort>(List<MyObject> list, Func<MyObject, TSort> sortExpression, bool ascending = true)
{
var t =
ascending
? list.OrderBy (sortExpression)
: list.OrderByDescending(sortExpression);
}
Or compile the expression on the fly:
void CreateSortedReport<TSort>(List<MyObject> list, Expression<Func<MyObject, TSort>> sortExpression, bool ascending = true)
{
var method = sortExpression.Compile();
var t =
ascending
? list.OrderBy (method)
: list.OrderByDescending(method);
}
You can build an extension method base on the built-in List(of T).Sort(IComparer(of T)).
public static class SortExtension
{
public static void SortBy<T, TProperty>(this List<T> list, Func<T, TProperty> orderby, bool ascending = true)
{
list.Sort(new InnerComparer<T, TProperty>(orderby, ascending));
}
class InnerComparer<T, TProperty> : IComparer<T>
{
private readonly Func<T, TProperty> _property;
private readonly int _ascending;
public InnerComparer(Func<T, TProperty> property, bool ascending)
{
_property = property;
_ascending = ascending ? 1 : -1;
}
int IComparer<T>.Compare(T x, T y)
{
var p1 = _property(x);
var p2 = _property(y);
return _ascending * Comparer<TProperty>.Default.Compare(p1, p2);
}
}
}
usage:
myObjects.SortBy(o => o.MyProperty);
Related
I have a custom sort that I am using to sort a list which works fine
public static void Sort<T>(ref List<T> list, string propertyName, SortDirection direction)
{
var comparer = new CustomComparer();
list = direction == SortDirection.Ascending
? list.OrderBy(x => x.GetType().GetProperty(propertyName).GetValue(x, null)).ToList()
: list.OrderByDescending(x => x.GetType().GetProperty(propertyName).GetValue(x, null)).ToList();
}
now I'm trying to add a CustomComparer to the mix and I get an error when I extend the method.
The type arguments for method 'IOrderedEnumerable
System.Linq.Enumerable.OrderBy(this
IEnumerable, Func, IComparer)' cannot be
inferred from the usage. Try specifying the type arguments explicitly.
public static void Sort<T>(ref List<T> list, string propertyName, SortDirection direction)
{
list = direction == SortDirection.Ascending
? list.OrderBy(x => x.GetType().GetProperty(propertyName).GetValue(x, null), new CustomComparer()).ToList()
: list.OrderByDescending(x => x.GetType().GetProperty(propertyName).GetValue(x, null), new CustomComparer()).ToList();
}
I get that the OrderBy is not set correctly does anyone have any suggestions?
Thanks.
public class CustomComparer : IComparer<object>
{
public int Compare(object x, object y)
{
}
}
Specify type arguments explicitly <T, object> in OrderByDescending method.
public class MyComparer : IComparer<object>
{
public int Compare(object x, object y)
{
throw new NotImplementedException();
}
}
public static void Sort<T>(ref List<T> list, string propertyName)
{
list = list.OrderByDescending<T, object>(x => x.GetType().GetProperty(propertyName).GetValue(x, null), new MyComparer()).ToList();
}
If I want the common elements in two list, I can use the intersect function:
var listC = listA.Intersect(listB);
But this compare objects. If the lists have objects of type Persons and I would like to get the persons with the same name for example, how could I do that? Where I set the condition of the name property?
Thanks.
Pass it a custom IEqualityComparer<T>.
First, make a class that implements that interface:
public class PersonNameEqualityComparer:IEqualityComparer<Person>
{
public int GetHashCode (Person obj)
{
return obj.Name.GetHashcode ();
}
public bool Equals (Person x, Person y)
{
return x.Name == y.Name;
}
}
Then, all you need to do is pass an instance of that IEqualityComparer to the intersect method.
var result = listA.Intersect(listB, new PersonNameEqualityComparer());
You could extend this to any object and any property, using generics and lambdas:
public class PropertyEqualityComparer<TObject, TProperty> : IEqualityComparer<TObject>
{
Func<TObject, TProperty> _selector;
IEqualityComparer<TProperty> _internalComparer;
public PropertyEqualityComparer(Func<TObject, TProperty> propertySelector, IEqualityComparer<TProperty> innerEqualityComparer = null)
{
_selector = propertySelector;
_internalComparer = innerEqualityComparer;
}
public int GetHashCode(TObject obj)
{
return _selector(obj).GetHashCode();
}
public bool Equals(TObject x, TObject y)
{
IEqualityComparer<TProperty> comparer = _internalComparer ?? EqualityComparer<TProperty>.Default;
return comparer.Equals(_selector(x), _selector(y));
}
}
You could then just use it like this:
var result = listA.Intersect(listB, new PropertyEqualityComparer<Person, string>(p => p.Name));
or like this:
var result = listA.Intersect(listB, new PropertyEqualityComparer<Person, string>(p => p.Age));
and so on.
I must use a self-coded comparer to sort a list of objects.
myObjectList.Sort(new MyComparer(_sortExpression, _sortDirection));
How can I append a 2nd expression and direction to sort by if values on 1st expression are equal?
I mean e.g.: Sort by last name and then if equal by first name. But I must use my own comparer.
myObjectList.Sort(
new MyComparer(new string[]{_exp1, _exp2}, new string[]{_dir1, _dir2}));
Thanks in advance!
Lord Vader
You could define an extension method that allows to chain comparers as follows:
public static class ComparerExtensions
{
public static IComparer<T> ThenBy<T>(this IComparer<T> comparer1,
IComparer<T> comparer2)
{
return new ChainedComparer<T>(comparer1, comparer2);
}
private class ChainedComparer<T> : IComparer<T>
{
private readonly IComparer<T> comparer1;
private readonly IComparer<T> comparer2;
public ChainedComparer(IComparer<T> comparer1,
IComparer<T> comparer2)
{
this.comparer1 = comparer1;
this.comparer2 = comparer2;
}
public int Compare(T x, T y)
{
int result = this.comparer1.Compare(x, y);
if (result == 0) result = this.comparer2.Compare(x, y);
return result;
}
}
}
Usage:
var comparer = new MyComparer(_sortExpression1, _sortDirection1).ThenBy(
new MyComparer(_sortExpression2, _sortDirection2));
myObjectList.Sort(comparer);
This sorts the list by the first comparer and, if two items are equal, then by the second comparer.
Alternative solution:
class ChainComparer<T> : IComparer<T>
{
private readonly IEnumerable<IComparer<T>> comparers;
public ChainComparer(params IComparer<T>[] comparers)
{
this.comparers = comparers;
}
public int Compare(T x, T y)
{
int result = 0;
foreach (var comparer in this.comparers)
{
result = comparer.Compare(x, y);
if (result != 0) break;
}
return result;
}
}
Usage:
myObjectList.Sort(new ChainComparer<MyClass>(
new MyComparer(_sortExpression1, _sortDirection1),
new MyComparer(_sortExpression2, _sortDirection2)));
You could just concatenate the two strings together and do a normal sort on that value. It would give you the same result. Or if you can use link you could do:
myObjectList.OrderBy(p => p.LastName).ThenBy(p => p.FirstName);
There are also OrderByDescending and ThenByDescending methods as well.
I have a LINQ Distinct() statement that uses my own custom comparer, like this:
class MyComparer<T> : IEqualityComparer<T> where T : MyType
{
public bool Equals(T x, T y)
{
return x.Id.Equals(y.Id);
}
public int GetHashCode(T obj)
{
return obj.Id.GetHashCode();
}
}
...
var distincts = bundle.GetAllThings.Distinct(new MyComparer<MySubType>());
This is all fine and dandy and works as I want. Out of curiosity, do I need to define my own Comparer, or can I replace it with a delegate? I thought I should be able to do something like this:
var distincts = bundle.GetAllThings.Distinct((a,b) => a.Id == b.Id);
But this doesn't compile. Is there a neat trick?
Distinct takes an IEqualityComparer as the second argument, so you will need an IEqualityComparer. It's not too hard to make a generic one that will take a delegate, though. Of course, this has probably already been implemented in some places, such as MoreLINQ suggested in one of the other answers.
You could implement it something like this:
public static class Compare
{
public static IEnumerable<T> DistinctBy<T, TIdentity>(this IEnumerable<T> source, Func<T, TIdentity> identitySelector)
{
return source.Distinct(Compare.By(identitySelector));
}
public static IEqualityComparer<TSource> By<TSource, TIdentity>(Func<TSource, TIdentity> identitySelector)
{
return new DelegateComparer<TSource, TIdentity>(identitySelector);
}
private class DelegateComparer<T, TIdentity> : IEqualityComparer<T>
{
private readonly Func<T, TIdentity> identitySelector;
public DelegateComparer(Func<T, TIdentity> identitySelector)
{
this.identitySelector = identitySelector;
}
public bool Equals(T x, T y)
{
return Equals(identitySelector(x), identitySelector(y));
}
public int GetHashCode(T obj)
{
return identitySelector(obj).GetHashCode();
}
}
}
Which gives you the syntax:
source.DistinctBy(a => a.Id);
Or, if you feel it's clearer this way:
source.Distinct(Compare.By(a => a.Id));
It's unfortunate that Distinct doesn't come up with such an overload, so what you have is a good option.
With MoreLinq, you can use the DistinctBy operator.
var distincts = bundle.GetAllThings.DistinctBy(a => a.Id);
You might also want to consider writing a generic ProjectionEqualityComparer that can turn the appropriate delegate into an IEqualityComparer<T> implementation, such as the one listed here.
Here is my perverse dirty little vanilla C# trick:
entities
.GroupBy(e => e.Id)
.Select(g => g.First())
This link shows how to create the extension method to be able to use Distinct in the manner you gave. You'll need to write two Distinct extension methods, and one IEqualityComparer.
Here's the code, from the site:
public static class Extensions
{
public static IEnumerable<T> Distinct<T>(this IEnumerable<T> source, Func<T, T, bool> comparer)
{
return source.Distinct(new DelegateComparer<T>(comparer));
}
public static IEnumerable<T> Distinct<T>(this IEnumerable<T> source, Func<T, T, bool> comparer, Func<T,int> hashMethod)
{
return source.Distinct(new DelegateComparer<T>(comparer,hashMethod));
}
}
public class DelegateComparer<T> : IEqualityComparer<T>
{
private Func<T, T, bool> _equals;
private Func<T,int> _getHashCode;
public DelegateComparer(Func<T, T, bool> equals)
{
this._equals = equals;
}
public DelegateComparer(Func<T, T, bool> equals, Func<T,int> getHashCode)
{
this._equals = equals;
this._getHashCode = getHashCode;
}
public bool Equals(T a, T b)
{
return _equals(a, b);
}
public int GetHashCode(T a)
{
if (_getHashCode != null)
return _getHashCode(a);
else
return a.GetHashCode();
}
}
As of NET6, a DistinctBy extension method was added to the library in System.Linq.
example:
Planet[] planets =
{
Planet.Mercury,
Planet.Venus,
Planet.Earth,
Planet.Mars,
Planet.Jupiter,
Planet.Saturn,
Planet.Uranus,
Planet.Neptune,
Planet.Pluto
};
foreach (Planet planet in planets.DistinctBy(p => p.Type))
{
Console.WriteLine(planet);
}
// This code produces the following output:
// Planet { Name = Mercury, Type = Rock, OrderFromSun = 1 }
// Planet { Name = Jupiter, Type = Gas, OrderFromSun = 5 }
// Planet { Name = Uranus, Type = Liquid, OrderFromSun = 7 }
// Planet { Name = Pluto, Type = Ice, OrderFromSun = 9 }
My Code looks like this :
Collection<NameValueCollection> optionInfoCollection = ....
List<NameValueCollection> optionInfoList = new List<NameValueCollection>();
optionInfoList = optionInfoCollection.ToList();
if(_isAlphabeticalSoting)
Sort optionInfoList
I tried optionInfoList.Sort() but it is not working.
Using the sort method and lambda expressions, it is really easy.
myList.Sort((a, b) => String.Compare(a.Name, b.Name))
The above example shows how to sort by the Name property of your object type, assuming Name is of type string.
If you just want Sort() to work, then you'll need to implement IComparable or IComparable<T> in the class.
If you don't mind creating a new list, you can use the OrderBy/ToList LINQ extension methods. If you want to sort the existing list with simpler syntax, you can add a few extension methods, enabling:
list.Sort(item => item.Name);
For example:
public static void Sort<TSource, TValue>(
this List<TSource> source,
Func<TSource, TValue> selector)
{
var comparer = Comparer<TValue>.Default;
source.Sort((x, y) => comparer.Compare(selector(x), selector(y)));
}
public static void SortDescending<TSource, TValue>(
this List<TSource> source,
Func<TSource, TValue> selector)
{
var comparer = Comparer<TValue>.Default;
source.Sort((x, y) => comparer.Compare(selector(y), selector(x)));
}
public class Person {
public string FirstName { get; set; }
public string LastName { get; set; }
}
List<Person> people = new List<Person>();
people.Sort(
delegate(Person x, Person y) {
if (x == null) {
if (y == null) { return 0; }
return -1;
}
if (y == null) { return 0; }
return x.FirstName.CompareTo(y.FirstName);
}
);
You need to set up a comparer that tells Sort() how to arrange the items.
Check out List.Sort Method (IComparer) for an example of how to do this...