I am using this snippet below for Ordering my Linq queries dynamically and works great. I am not great at reflection or complex linq queries but I need a way that when ascendin
My approach is to create a generic class that implements IComparer
. This way you can use your class in all LINQ statements with a non-default comparer. The advantage is that you will have full type checking at compile time. You can't name properties that can't be compared or that can't be null
class NullValueLastComparer : IComparer
where TClass : class
where TKey : IComparable
{
This generic class has two Type parameters: the class that you want to compare, and the type of the property you want to compare with. The where clauses assert that TClass
is a reference type, so you can access Properties, and TKey
is something that implements normal comparison.
To create objects for the class we have two Factory functions. Both functions need a KeySelector, similar to lots of Key Selectors you can find in LINQ. The KeySelector function is the function that will tell you which property must be used in your comparisons. It is similar to the KeySelector in function Enumerable.Where
.
The second Create function gives you the possibility to provide a non-default comparer, again similar to a lot of functions in the Enumerable class:
public static IComparer Create(Func keySelector)
{ // call the other Create function, with the default TKey comparer
return Create(keySelector, Comparer.Default);
}
public static IComparer Create(Func keySelector, IComparer comparer)
{ // construct a null value last comparer object
// initialize with the key selector and the key comparer
return new NullValueLastComparer()
{
KeySelector = keySelector,
KeyComparer = comparer,
};
}
I use a private constructor. Only the static create classes can construct the null value last comparer
private NullValueLastComparer() { }
Two properties: the key selector and the comparer:
private Func KeySelector { get; set; }
private IComparer KeyComparer { get; set; }
The actual compare function. It will use the KeySelector to get the values that must be compared, and compares them such that a null value will be last.
public int Compare(TClass x, TClass y)
{
if (Object.ReferenceEquals(x, null))
throw new ArgumentNullException(nameof(x));
if (Object.ReferenceEquals(y, null)
throw new ArgumentNullException(nameof(y));
// get the values to compare
TKey keyX = KeySelector(x);
TKey keyY = KeySelector(y);
return this.Compare(keyX, keyY);
}
The private function that compares the Keys such that null values will be last
private int Compare(TKey x, TKey y)
{ // compare such that null values last, or if both not null, use IComparable
if (Object.ReferenceEquals(x, null))
{
if (Object.ReferenceEquals(y, null))
{ // both null
return 0;
}
else
{ // x null, y not null => x follows y
return +1;
}
}
else
{ // x not null
if (Object.ReferenceEquals(y, null))
{ // x not null; y null: x precedes y
return -1;
}
else
{
return this.KeyComparer.Compare(x, y);
}
}
}
}
Usage:
class Person
{
public string FirstName {get; set;}
public string FamilyName {get; set;}
}
// create a comparer that will put Persons without firstName last:
IComparer myComparer =
NullValueLastComparer.Create(person => person.FirstName);
Person person1 = ...;
Person person2 = ...;
int compareResult = myComparer.Compare(person1, person2);
This compare will compare Persons. When two Persons are compared, it will take person.FirstName for both persons, and will put the one without FirstName as last.
Usage in a complicated LINQ statement. Note that there is full type checking at compile time.
IEnumerable myPersonCollection = ...
var sortedPersons = myPersonCollection
.OrderBy(person => person, myComparer)
.ThenBy(person => person.LastName)
.Select(person => ...)
.ToDictonary(...)