I am trying to order an IEnumerable<MyClass>
based on the index of the selected property:
class MyClass
{
public string Name;
public string Value;
}
I have a helper method to get an Expression<>
of the criteria.
It is compiled to create the Func<>
to help with the ordering.
I'm having trouble trying to make the Func<>
return a Generic TKey
to be ordered.
public Expression<Func<T, TKey>> Helper<T>(int propIdx)
{
var param = Expression.Parameter(typeof(T), "record");
var props = typeof(T).GetProperties();
if(propIdx > props.Count())
return Expression.Lambda<Func<T, TKey>>)(Expression.Constant(true), param);
Expression propExp = Expression.Property(param, props[propIdx].Name);
return lambda = Expression.Lambda<Func<T, TKey>>(propExp, param);
}
This is used to help with dynamic order criteria.
OrderByIdx(IEnumerable<MyClass> input, int propIdx)
{
Expression<Func<T, TKey>> exp = Helper(propIdx);
var funct = exp.Compile();
return input.OrderBy(funct);
}
I am getting an error The type or namespace TKey could not be found
How can I use a Generic<TKey>
to help with the ordering ?
First of all: Type.GetProperties
doesn't have a defined order.
var props = typeof(T).GetProperties();
Which property will be in props[0]
? And are all properties readable?
So if you want to get a defined order, my advice would be to do some sorting. For instance get the public readable properties ordered by name.
Furthermore, isn't it a bit weird if users of your code (= software, not operators) want to use your generic code based on an index?
MyClass
with a property Date
Date
Date
, or by property named "Date", They have to determine the index of property Date
: Date seems to be the fourth propertyWouldn't it be easier if they could just say: "order by property Date
", or "order by the property that is named 'Date'?
IEnumerable<MyClass> source = ...
var orderedSource = source.OrderBy(t => t.Date);
Sometimes your users don't have access to the property, they only know the name of the property. In that case you could create an extension method where you provide the name of a property, and that returns the ordered source.
If you are not familiar with extension methods. See extension methods demystified
Usage would be something like:
var orderedSource = source.OrderBy(nameof(MyClass.Date));
or for instance: "order by the selected column in my DataGridView".
string propertyName = this.DataGridView.Columns.Cast<DataGridViewColumn>()
.Where(column => column.IsSelected)
.Select(column => column.DataPropertyName)
.FirstOrDefault();
var orderedSource = source.OrderBy(propertyName);
Such a procedure would be easy to make, easy to use and reuse, easy to test, easy to maintain. And above all: they are one-liners:
public static IOrderedEnumerable<T> OrderBy<T>(
this IEnumerable<T> source,
string propertyName)
{
// TODO: handle invalid input
PropertyInfo propertyInfo = typeof(T).GetProperty(propertyName);
// TODO: handle invalid propertyname
return source.OrderBy(propertyInfo);
}
public static IOrderedEnumerable<T> OrderBy<T>(
this IEnumerable<T> source,
PropertyInfo propertyInfo)
{
// TODO: handle invalid parameters
return source.OrderBy(t => propertyInfo.GetValue(t) as propertyInfo.PropertyType);
}
If desired you could create overloads for ThenBy(this IEnumerable<T>, ...)
.
But I want to order by Index, not by Name!
If you really want to sort by index, it is easier to create extension methods with Lambda expressions, then to fiddle with Expressions.
Consider the following extension methods:
public static IOrderedEnumerable<T> OrderBy<T>(
IEnumerable<T> source,
int propertyIndex)
{
return source.OrderBy(propertyIndex, GetDefaultPropertyOrder(typeof(T));
}
public static IOrderedEnumerable<T> OrderBy<T>(
IEnumerable<T> source,
int propertyIndex,
IReadOnlyList<PropertyInfo> properties)
{
// TODO: handle null and out-of-range parameters
PropertyInfo sortProperty = properties[propertyIndex];
return source.OrderBy(sortProperty);
}
public static IList<PropertyInfo> GetDefaultPropertyOrder(Type t)
{
return t.GetProperties()
.Where(property => property.CanRead)
.OrderBy(property => property.Name)
.ToList();
}
Usage:
BindingList<MyClass> myObjects = ...
// display myObjects in a dataGridView
this.DataGridView.DataSource = myObjects;
// event if operator clicks on column header:
public void OnColumnHeaderClicked(object sender, ...)
{
DataGridViewColumn clickedColumn = GetClickedColumn();
// sort by column index, as you prefer:
var sortedObjects = myObjects.SortBy(clickedColumn.DisplayIndex);
ProcessSortedObjects(sortedObjects);
// but of course, you skip some intermediate methods if you sort by property name
var sortedObject = myObject.SortBy(clickedColumn.DataPropertyName);
ProcessSortedObjects(sortedObjects);
}