How can I dynamically store expressions used for Linq Orderby?
c#, expression, linq
Solution
The generic argument is inferred like so:
IOrderedEnumerable<TSource> OrderBy<TSource, TKey>(this IEnumerable<TSource> enumerable, Func<TSource, TKey> expression)
When you have an `IEnumerable<T>`, the compiler is able to infer that the `TSource` is `T` in this situation because of the extension method declaration; so the extension method is already added on knowing what `TSource` is. For example:
Enumerable.Range(0, 10).OrderBy(x => x)
Since we start with an `IEnumerable<int>`, the compiler can infer that the expression it expects is `Func<int, TKey>`, because the extension is affecting the `IEnumerable<int>`. Next, because your expression returns a value, the compiler can infer the remaining type, in this situation `int`, so it becomes a `Func<int, int>` with this example.
Now, germane to your particular problem, you could easily set up your expression to work appropriately if you genericize your `SortConfig` object appropriately. It looks like your `SortConfig` takes a `Func<TSource, object>` delegate right now. If you genericize your `SortConfig` to use another type, you gain specificity. Example:
void Add<TSource, TKey>(string name, Func<TSource, TKey> expression)
The next problem here is how to store your backing methods in some format. And your class declaration looks like so:
public class SortConfig<TSource>
Then all the data types should line up when you invoke the `OrderBy` extension.
EDIT: Here's a working example of what I think you want to do:
static void Main(string[] args)
{
var list = Enumerable.Range(0, 10).Reverse().Select(x => new SampleClass { IntProperty = x, StringProperty = x + "String", DateTimeProperty = DateTime.Now.AddDays(x * -1) });
SortContainer<SampleClass> container = new SortContainer<SampleClass>();
container.Add("Int", x => x.IntProperty);
container.Add("String", x => x.StringProperty);
container.Add("DateTime", x => x.DateTimeProperty);
var sorter = container.GetSorterFor("Int");
sorter.Sort(list).ForEach(x => Console.WriteLine(x.IntProperty));
Console.ReadKey();
}
public class SampleClass
{
public int IntProperty { get; set; }
public string StringProperty { get; set; }
public DateTime DateTimeProperty { get; set; }
}
public class SortContainer<TSource>
{
protected Dictionary<string, ISorter<TSource>> _sortTypes = new Dictionary<string, ISorter<TSource>>();
public void Add<TKey>(string name, Func<TSource, TKey> sortExpression)
{
Sorter<TSource, TKey> sorter = new Sorter<TSource, TKey>(sortExpression);
_sortTypes.Add(name, sorter);
}
public ISorter<TSource> GetSorterFor(string name)
{
return _sortTypes[name];
}
}
public class Sorter<TSource, TKey> : ISorter<TSource>
{
protected Func<TSource, TKey> _sortExpression = null;
public Sorter(Func<TSource, TKey> sortExpression)
{
_sortExpression = sortExpression;
}
public IOrderedEnumerable<TSource> Sort(IEnumerable<TSource> sourceEnumerable)
{
return sourceEnumerable.OrderBy(_sortExpression);
}
}
public interface ISorter<TSource>
{
IOrderedEnumerable<TSource> Sort(IEnumerable<TSource> sourceEnumerable);
}
Problem
I am trying to create a quick class so that I can make writing sorting code for a grid much easier to work with and maintain, and to keep code repetition down. To do this I came up with the following class: ``` public class SortConfig<TSource, TRelatedObject> where TSource : class where TRelatedObject : class { public IList<SortOption> Options { get; protected set; } public SortOption DefaultOption { get; set; } public SortConfig() { Options = new List<SortOption>(); } public void Add(string name, Expression<Func<TSource, object>> sortExpression, TRelatedObject relatedObject, bool isDefault = false) { var option = new SortOption { FriendlyName = name, SortExpression = sortExpression, RelatedObject = relatedObject }; Options.Add(option); if (isDefault) DefaultOption = option; } public SortOption GetSortOption(string sortName) { if (sortName.EndsWith("asc", StringComparison.OrdinalIgnoreCase)) sortName = sortName.Substring(0, sortName.LastIndexOf("asc", StringComparison.OrdinalIgnoreCase)); else if (sortName.EndsWith("desc", StringComparison.OrdinalIgnoreCase)) sortName = sortName.Substring(0, sortName.LastIndexOf("desc", StringComparison.OrdinalIgnoreCase)); sortName = sortName.Trim(); var option = Options.Where(x => x.FriendlyName.Trim().Equals(sortName, StringComparison.OrdinalIgnoreCase)) .FirstOrDefault(); if (option == null) { if (DefaultOption == null) throw new InvalidOperationException( string.Format("No configuration found for sort type of '{0}', and no default sort configuration exists", sortName)); option = DefaultOption; } return option; } public class SortOption { public string FriendlyName { get; set; } public Expression<Func<TSource, object>> SortExpression { get; set; } public TRelatedObject RelatedObject { get; set; } } } ``` The idea is that you create a quick configuration of the different sort options, what OrderBy expression is used for that, and optionally an object that is related to that sort option. This allows my code to look like: ``` protected void InitSortConfig() { _sortConfig = new SortConfig<xosPodOptimizedSearch, HtmlAnchor>(); _sortConfig.Add("name", (x => x.LastName), lnkSortName, true); _sortConfig.Add("team", (x => x.SchoolName), lnkSortTeam); _sortConfig.Add("rate", (x => x.XosRating), lnkSortRate); _sortConfig.Add("pos", (x => x.ProjectedPositions), null); _sortConfig.Add("height", (x => x.Height), lnkSortHeight); _sortConfig.Add("weight", (x => x.Weight), lnkSortWeight); _sortConfig.Add("city", (x => x.SchoolCity), lnkSortCity); _sortConfig.Add("state", (x => x.SchoolState), lnkSortState); } ``` and then I can sort by just doing ``` // Get desired sorting configuration InitSortConfig(); var sortOption = _sortConfig.GetSortOption(sort); bool isDescendingSort = sort.EndsWith("desc", StringComparison.OrdinalIgnoreCase); // Setup columns InitSortLinks(); if (sortOption.RelatedObject != null) { // Make modifications to html anchor } // Form query var query = PodDataContext.xosPodOptimizedSearches.AsQueryable(); if (isDescendingSort) query = query.OrderByDescending(sortOption.SortExpression); else query = query.OrderBy(sortOption.SortExpression); ``` This works great when the sorted variable is a string, but when it is not a string I get the following exception: `Cannot order by type 'System.Object'.` I'm assuming this is because I am storing the expression as `Expression<Func<TSource, object>>` and not being more specific about that 2nd generic. I don't understand how I can keep all my different sort options (even for non-string properties) in one class. I guess one of the issues is that the `Linq.OrderBy()` clause takes `Expression<Func<TSource, TKey>>` as it's parameter, but I am not wrapping my head around how `Linq.OrderBy()` is able to infer what `TKey` should be, and therefore I cannot understand how to take advantage of that inference to store these expressions with the proper `TKey`. Any ideas?