Dynamically build lambda expression from a collection of objects?
Asked Answered
U

2

8

I have a list of sorts stored in this format:

public class ReportSort
{
    public ListSortDirection SortDirection { get; set; }
    public string Member { get; set; }
}

And I need to turn it into a lambda expression of type Action<DataSourceSortDescriptorFactory<TModel>>

So assuming I have the following collection of Report Sorts as:

new ReportSort(ListSortDirection.Ascending, "LastName"),
new ReportSort(ListSortDirection.Ascending, "FirstName"),

I would need to transform it into such a statement to be used like so:

.Sort(sort => { 
        sort.Add("LastName").Ascending();
        sort.Add("FirstName").Ascending();
      })

And the sort method signature is:

public virtual TDataSourceBuilder Sort(Action<DataSourceSortDescriptorFactory<TModel>> configurator)

So I have some method right now:

public static Action<DataSourceSortDescriptorFactory<TModel>> ToGridSortsFromReportSorts<TModel>(List<ReportSort> sorts) where TModel : class
    {
        Action<DataSourceSortDescriptorFactory<TModel>> expression;
        //stuff I don't know how to do
        return expression;
    }

...and I have no idea what to do here.

EDIT: Answer is:

var expression = new Action<DataSourceSortDescriptorFactory<TModel>>(x =>
        {
            foreach (var sort in sorts)
            {
                if (sort.SortDirection == System.ComponentModel.ListSortDirection.Ascending)
                {
                    x.Add(sort.Member).Ascending();
                }
                else
                {
                    x.Add(sort.Member).Descending();
                }
            }
        });

I was thinking at first I had to dynamically build a lambda expression from scratch using the Expression class. Luckily that wasn't the case.

Unsightly answered 27/2, 2018 at 23:0 Comment(0)
P
5

...and I have no idea what to do here.

Well, reason it out.

What have you got in hand? A List<ReportSort> called sorts.

What do you need? An Action<Whatever>.

You've already taken the first step: you've make a method that takes the thing you have and returns the thing you need. Great first step.

    Action<DataSourceSortDescriptorFactory<TModel>> expression;
    //stuff I don't know how to do
    return expression;

And you've called out what you don't know how to do -- yet. This is a good technique.

Start by filling in something that compiles but doesn't work properly.

Action<DataSourceSortDescriptorFactory<TModel>> expression = 
  sort => {         
    sort.Add("LastName").Ascending();
    sort.Add("FirstName").Ascending();
  };
return expression;

Excellent. Now you have a compiling program which means you can run your tests and verify that if this case is expected, the test passes, and if anything else is expected, the test fails.

Now think, what have I got in hand? I've got a list of stuff, and I'm doing an Action. That means that a side effect is happening, probably involving every item on the list. So there's probably a foreach in there somewhere:

Action<DataSourceSortDescriptorFactory<TModel>> expression = 
  sort => {         
    sort.Add("LastName").Ascending();
    sort.Add("FirstName").Ascending();
    foreach(var sort in sorts) { 
      // Do something
    }
  };
return expression;

Compile it. It fails. Ah, we have confused the sort we are adding to with the new sort we are adding. Fix the problem.

Action<DataSourceSortDescriptorFactory<TModel>> expression = 
  existingSort => {         
    existingSort.Add("LastName").Ascending();
    existingSort.Add("FirstName").Ascending();
    foreach(var newSort in sorts) { 
      // Do something
    }
  };
return expression;

Great, now again we are in a position to compile and run tests.

The pattern here should be clear. Keep it compiling, keep running tests, gradually make your program more and more correct, reason about the operations you can perform on the values that you have in hand.

Can you finish it off?

Papandreou answered 27/2, 2018 at 23:24 Comment(2)
Yeah I was able to figure it out, thanks for posting a detailed answer. I thought I would have to manually build a lambda expression from scratch, which is where I get dizzy trying to understand it, this wasn't as hard as I feared.Unsightly
@SventoryMang: It is possible to build a lambda entirely from scratch and compile it, using a variety of techniques. But it is usually not necessary.Papandreou
P
1

You could use the following lambda expression that you could assign to the Action<T> delegate. In that lambda expression, capture the List<T> variable and loop over it:

public static Action<DataSourceSortDescriptorFactory<TModel>> ToGridSortsFromReportSorts<TModel>(List<ReportSort> sorts) where TModel : class
{
    Action<DataSourceSortDescriptorFactory<TModel>> expression = 
       result  => 
       {
           foreach (var sort in sorts)
           {
                if (sort.SortDirection == ListSortDirection.Ascending)
                    result.Add(sort.Member).Ascending();
                else // or whatever other methods you want to handle here
                    result.Add(sort.Member).Descending();
           }
       };
    return expression;
}
Plesiosaur answered 27/2, 2018 at 23:21 Comment(2)
Note that delegate (T t) { ... } is poor style in code written after C# 3 shipped. Instead use (T t) => { ... }Papandreou
@EricLippert yeah idk why i wrote that. fixedPlesiosaur

© 2022 - 2024 — McMap. All rights reserved.