Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamically build lambda expression from a collection of objects?

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.

like image 885
SventoryMang Avatar asked Feb 27 '18 23:02

SventoryMang


People also ask

Can we use dynamic in lambda expression?

In 2010, the Dynamic Type was introduced and that gave us the ability to create dynamic lambda expressions.

What is the use of lambda expression in C#?

Lambda expressions and tuples The C# language provides built-in support for tuples. You can provide a tuple as an argument to a lambda expression, and your lambda expression can also return a tuple. In some cases, the C# compiler uses type inference to determine the types of tuple components.

What is Expression C#?

An expression in C# is a combination of operands (variables, literals, method calls) and operators that can be evaluated to a single value. To be precise, an expression must have at least one operand but may not have any operator.


2 Answers

...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?

like image 130
Eric Lippert Avatar answered Oct 02 '22 18:10

Eric Lippert


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;
}
like image 34
adjan Avatar answered Oct 02 '22 19:10

adjan