Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    text
    copied!<p>Just stumbled into this oldie...</p> <p>To do this without the dynamic LINQ library, you just need the code as below. This covers most common scenarios including nested properties.</p> <p>To get it working with <code>IEnumerable&lt;T&gt;</code> you could add some wrapper methods that go via <code>AsQueryable</code> - but the code below is the core <code>Expression</code> logic needed.</p> <pre><code>public static IOrderedQueryable&lt;T&gt; OrderBy&lt;T&gt;( this IQueryable&lt;T&gt; source, string property) { return ApplyOrder&lt;T&gt;(source, property, "OrderBy"); } public static IOrderedQueryable&lt;T&gt; OrderByDescending&lt;T&gt;( this IQueryable&lt;T&gt; source, string property) { return ApplyOrder&lt;T&gt;(source, property, "OrderByDescending"); } public static IOrderedQueryable&lt;T&gt; ThenBy&lt;T&gt;( this IOrderedQueryable&lt;T&gt; source, string property) { return ApplyOrder&lt;T&gt;(source, property, "ThenBy"); } public static IOrderedQueryable&lt;T&gt; ThenByDescending&lt;T&gt;( this IOrderedQueryable&lt;T&gt; source, string property) { return ApplyOrder&lt;T&gt;(source, property, "ThenByDescending"); } static IOrderedQueryable&lt;T&gt; ApplyOrder&lt;T&gt;( IQueryable&lt;T&gt; source, string property, string methodName) { string[] props = property.Split('.'); Type type = typeof(T); ParameterExpression arg = Expression.Parameter(type, "x"); Expression expr = arg; foreach(string prop in props) { // use reflection (not ComponentModel) to mirror LINQ PropertyInfo pi = type.GetProperty(prop); expr = Expression.Property(expr, pi); type = pi.PropertyType; } Type delegateType = typeof(Func&lt;,&gt;).MakeGenericType(typeof(T), type); LambdaExpression lambda = Expression.Lambda(delegateType, expr, arg); object result = typeof(Queryable).GetMethods().Single( method =&gt; method.Name == methodName &amp;&amp; method.IsGenericMethodDefinition &amp;&amp; method.GetGenericArguments().Length == 2 &amp;&amp; method.GetParameters().Length == 2) .MakeGenericMethod(typeof(T), type) .Invoke(null, new object[] {source, lambda}); return (IOrderedQueryable&lt;T&gt;)result; } </code></pre> <hr> <p>Edit: it gets more fun if you want to mix that with <code>dynamic</code> - although note that <code>dynamic</code> only applies to LINQ-to-Objects (expression-trees for ORMs etc can't really represent <code>dynamic</code> queries - <code>MemberExpression</code> doesn't support it). But here's a way to do it with LINQ-to-Objects. Note that the choice of <code>Hashtable</code> is due to favorable locking semantics:</p> <pre><code>using Microsoft.CSharp.RuntimeBinder; using System; using System.Collections; using System.Collections.Generic; using System.Dynamic; using System.Linq; using System.Runtime.CompilerServices; static class Program { private static class AccessorCache { private static readonly Hashtable accessors = new Hashtable(); private static readonly Hashtable callSites = new Hashtable(); private static CallSite&lt;Func&lt;CallSite, object, object&gt;&gt; GetCallSiteLocked( string name) { var callSite = (CallSite&lt;Func&lt;CallSite, object, object&gt;&gt;)callSites[name]; if(callSite == null) { callSites[name] = callSite = CallSite&lt;Func&lt;CallSite, object, object&gt;&gt; .Create(Binder.GetMember( CSharpBinderFlags.None, name, typeof(AccessorCache), new CSharpArgumentInfo[] { CSharpArgumentInfo.Create( CSharpArgumentInfoFlags.None, null) })); } return callSite; } internal static Func&lt;dynamic,object&gt; GetAccessor(string name) { Func&lt;dynamic, object&gt; accessor = (Func&lt;dynamic, object&gt;)accessors[name]; if (accessor == null) { lock (accessors ) { accessor = (Func&lt;dynamic, object&gt;)accessors[name]; if (accessor == null) { if(name.IndexOf('.') &gt;= 0) { string[] props = name.Split('.'); CallSite&lt;Func&lt;CallSite, object, object&gt;&gt;[] arr = Array.ConvertAll(props, GetCallSiteLocked); accessor = target =&gt; { object val = (object)target; for (int i = 0; i &lt; arr.Length; i++) { var cs = arr[i]; val = cs.Target(cs, val); } return val; }; } else { var callSite = GetCallSiteLocked(name); accessor = target =&gt; { return callSite.Target(callSite, (object)target); }; } accessors[name] = accessor; } } } return accessor; } } public static IOrderedEnumerable&lt;dynamic&gt; OrderBy( this IEnumerable&lt;dynamic&gt; source, string property) { return Enumerable.OrderBy&lt;dynamic, object&gt;( source, AccessorCache.GetAccessor(property), Comparer&lt;object&gt;.Default); } public static IOrderedEnumerable&lt;dynamic&gt; OrderByDescending( this IEnumerable&lt;dynamic&gt; source, string property) { return Enumerable.OrderByDescending&lt;dynamic, object&gt;( source, AccessorCache.GetAccessor(property), Comparer&lt;object&gt;.Default); } public static IOrderedEnumerable&lt;dynamic&gt; ThenBy( this IOrderedEnumerable&lt;dynamic&gt; source, string property) { return Enumerable.ThenBy&lt;dynamic, object&gt;( source, AccessorCache.GetAccessor(property), Comparer&lt;object&gt;.Default); } public static IOrderedEnumerable&lt;dynamic&gt; ThenByDescending( this IOrderedEnumerable&lt;dynamic&gt; source, string property) { return Enumerable.ThenByDescending&lt;dynamic, object&gt;( source, AccessorCache.GetAccessor(property), Comparer&lt;object&gt;.Default); } static void Main() { dynamic a = new ExpandoObject(), b = new ExpandoObject(), c = new ExpandoObject(); a.X = "abc"; b.X = "ghi"; c.X = "def"; dynamic[] data = new[] { new { Y = a }, new { Y = b }, new { Y = c } }; var ordered = data.OrderByDescending("Y.X").ToArray(); foreach (var obj in ordered) { Console.WriteLine(obj.Y.X); } } } </code></pre>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload