1

My goal here is to create a method SortRecords that accepts an IEnumerable<T> and a PropertyInfo as parameters. The IEnumerable<T> is a list of records. The PropertyInfo is a property of of T. When invoked, SortRecords should invoke the Enumerable.SortBy<T, typeof Property> method with x => x.Property. Note here that Enumerable.SortBy has two generic parameters. Also, reflection cannot be used inside the lambda expression because (a) it is slow and (b) it won't work with Entity Framework.

I have written some code, but I keep seeing the error message Operation could destabilize the runtime. Here is what my code looks like

for (int i = 0; i < NumberOfSorts; i++)
        {
            string propertyName = PropertyNames[ColumnSortOrder[i]];
            PropertyInfo property = typeof(T).GetProperties().Single(p => p.Name == propertyName);              

            Func<IEnumerable<T>, PropertyInfo, IEnumerable<T>> sortingFunction = GetFunctionToSortRecords<T>(filteredRecords, property);
            sortedRecords = GetFunctionToSortRecords<T>(filteredRecords, property)(filteredRecords, property);
         }

end first code snippet

Method definitions follow

delegate IEnumerable<T> GetFunctionToSortRecordsDelegate<T>(IEnumerable<T> records, PropertyInfo propertyToSortOn);
public static Func<IEnumerable<T>, PropertyInfo, IEnumerable<T>> GetFunctionToSortRecords<T>(IEnumerable<T> records, PropertyInfo propertyToSortOn)
    {
        Type propertyType = propertyToSortOn.GetType();

        DynamicMethod method = new DynamicMethod("SortRecords", typeof(IEnumerable<T>), new Type[] { typeof(IEnumerable<T>), typeof(PropertyInfo) });            
        ILGenerator generator = method.GetILGenerator();            

        MethodInfo GetPropertyValue = propertyToSortOn.GetGetMethod();
        MethodInfo GetDefaultKeySelectorForProperty = typeof(DataTablesSorting).GetMethod("GetDefaultKeySelectorForProperty")                                                                                                         
            .MakeGenericMethod(new Type[] {typeof(T), propertyToSortOn.PropertyType });            

        MethodInfo EnumerableOrderBy = typeof(Enumerable).GetMethods()
            .Single(m => m.Name == "OrderBy" && m.GetParameters().Count()==3);

        // Get the default key selector for the property passed in.            
        generator.Emit(OpCodes.Ldarg_1); // property
        generator.Emit(OpCodes.Call, GetDefaultKeySelectorForProperty);

        // Save the default key selector at location 0
        generator.Emit(OpCodes.Stloc_0);

        generator.Emit(OpCodes.Ldarg_0); // records
        generator.Emit(OpCodes.Ldloc_0); // default key selector
        generator.Emit(OpCodes.Call, EnumerableOrderBy);
        generator.Emit(OpCodes.Ret);

        return ((GetFunctionToSortRecordsDelegate<T>)(method.CreateDelegate(typeof(GetFunctionToSortRecordsDelegate<T>)))).Invoke;
    }

    delegate TKey GetDefaultKeySelectorForPropertyDelegate<T, TKey>(T t);
    public static Func<T, TKey> GetDefaultKeySelectorForProperty<T, TKey>(PropertyInfo property)
    {
        DynamicMethod method = new DynamicMethod("GetKeySelector", typeof(TKey), new Type[] { typeof(T) });
        ILGenerator generator = method.GetILGenerator();

        MethodInfo GetPropertyValue = property.GetGetMethod();
        generator.Emit(OpCodes.Ldarg_0);
        generator.Emit(OpCodes.Callvirt, GetPropertyValue);
        generator.Emit(OpCodes.Ret);

        return ((GetDefaultKeySelectorForPropertyDelegate<T, TKey>)(method.CreateDelegate(typeof(GetDefaultKeySelectorForPropertyDelegate<T, TKey>)))).Invoke;
    }

I think that this question may be related: DynamicMethod with generic type parameters

Community
  • 1
  • 1
Vivian River
  • 31,198
  • 62
  • 198
  • 313

2 Answers2

0

I haven't used DynamicMethod myself like this, but I suspect you just need to MakeGenericMethod on this EnumerableOrderBy just like you're already doing for GetDefaultKeySelectorForProperty. At the moment you're trying to call the generic method without specifying any type arguments.

So something like:

MethodInfo EnumerableOrderBy = typeof(Enumerable).GetMethods()
    .Single(m => m.Name == "OrderBy" && m.GetParameters().Count() == 3)
    .MakeGenericMethod(typeof(T), propertyToSortOn.PropertyType);

(MakeGenericMethod uses a parameter array, so you don't need to explicitly construct the Type[] to pass in.)

(If you need to work with Entity Framework, I've have thought you'd be looking at Queryable rather than Enumerable and building expression trees instead of delegates, but that's another matter.)

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • You've made an important observation, no doubt, but even when I apply the change you suggest, I'm still getting the `Operation could destabilize the runtime` error. – Vivian River Aug 15 '13 at 14:58
  • @DanielAllenLangdon: At which point? A short but complete example would help here - if you can reproduce it with a shorter block of IL (e.g. pass in the ordering delegate) that would help too. – Jon Skeet Aug 15 '13 at 15:19
  • I tried rewriting the code using reflection and found that I needed the two-parameter `OrderBy` method. I think that this is definitely one problem, but I'm still getting that same error message when trying to use Dynamic Method. I think that perhaps the solution might be to cleanly wrap the generic OrderBy method in a method that uses Dynamic method and invoke that... – Vivian River Aug 15 '13 at 15:27
  • @DanielAllenLangdon: You *should* be able to get it to work. I would reduce the problem to a dynamic method which *just* acts as a proxy to `OrderBy`, and go from there. (Sorry for not spotting that you needed the two-parameter `OrderBy` call... I should have seen that.) – Jon Skeet Aug 15 '13 at 15:32
0

I prefer to use Expressions for this kind of problem. Here is an example that should work for you.

    public static Func<IEnumerable<T>, PropertyInfo, IEnumerable<T>> GetFunctionToSortRecords<T>(IEnumerable<T> records, PropertyInfo property)
    {
        var propertyExpression = GetExpressionForProperty<T>(property);
        var method = typeof(TheCurrentClass).GetMethod("InternalGetFunctionToSortRecords", BindingFlags.NonPublic | BindingFlags.Static);

        return (Func<IEnumerable<T>, PropertyInfo, IEnumerable<T>>)method.MakeGenericMethod(typeof(T), property.PropertyType).Invoke(null, new object[] { propertyExpression });
    }

    private static Func<IEnumerable<T>, PropertyInfo, IEnumerable<T>> InternalGetFunctionToSortRecords<T, TProp>(Expression propertyExpression)
    {
        var lambdaExpression = propertyExpression as LambdaExpression;
        Func<T, TProp> keySelector = (Func<T, TProp>)lambdaExpression.Compile();
        Func<IEnumerable<T>, PropertyInfo, IEnumerable<T>> sorter = (x, y) => x.OrderBy(keySelector);

        return sorter.Invoke;
    }

    private static Expression GetExpressionForProperty<T>(PropertyInfo property)
    {
        var parameter = Expression.Parameter(typeof(T));
        var propertyExpression = Expression.Property(parameter, property);
        var lambdaExpression = Expression.Lambda(propertyExpression, parameter);

        return lambdaExpression;
    }
Grax32
  • 3,986
  • 1
  • 17
  • 32