Tuesday, June 23, 2009

Exploring Lambdas With Func<T>, Expression<T> and an Interesting Application

Some time ago, I was working on a Windows Forms application for a personal project (more on overclockregister.com soon!) and I had a bunch of reference data that I wanted a quick and dirty way of editing without going to the effort of designing a form for each type, or even defining grid columns for each type.

It's worth noting that I'm not sure I would use the approach presented in this post, it's probably not appropriate for some domain designs or large systems. It ended up being more of an investigation into the Expression<T> class. I also investigated a variant of this approach using a trick with generics that I might post about some time.

A simple form for the above purpose could consist of a combo box containing all reference data types and a data grid below that. Selecting a reference data type populates the grid with all instances of the data type for editing and creation of new instances.

OK, so after we've populated our combo box, we need to handle the SelectedIndexChanged event and populate the grid. Start writing that switch statement! Err... no... wait! Switch statements are evil! We should try to reduce the number of places we need to modify code if we change the set of reference data.

What if we told the combo box how to get the data to populate the grid with? That would allow us to keep the logic for populating the combo box and the grid in one place. Enter Func and lambda expressions! The DelegateComboBoxItem, version 1.

    class DelegateComboBoxItem
    {
        public Func<IList> DataMember { get; set; }
        public string DisplayName { get; set; }
    }
The DataMember property is called to get the related data for this item in the combo box. Used like so:
        private void Form1_Load(object sender, EventArgs e)
        {
            comboBox1.DisplayMember = "DisplayName";
            comboBox1.Items.Add(new DelegateComboBoxItem
                { 
                    DisplayName = "Students", 
                    DataMember = () => Repository.Students 
                });
            comboBox1.Items.Add(new DelegateComboBoxItem
                { 
                    DisplayName = "Teachers", 
                    DataMember = () => Repository.Teachers 
                });
        }

        private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
        {
            DelegateComboBoxItem item = (DelegateComboBoxItem)comboBox1.SelectedItem;
            dataGridView1.DataSource = item.DataMember.Invoke();
        }

Putting aside formatting of the grid, we now have a nice easy way to declare the values in the combo box and the data sources for the data grid in the same place. Our SelectedIndexChanged event handler is clean, no nasty switch statements!

So our data grid is populated and allows us to edit the existing records, but what about adding new records? This requires handling the AddingNew event of a BindingSource object. Setting the NewObject property on the AddingNewEventArgs parameter of the event handler allows you to add new objects to the underlying list and have them displayed in the grid without rebinding. But how do we know what type to instantiate?

Let's assume that our Repository methods return generic Lists, so Repository.Students returns List<Student> and Repository.Teachers returns List<Teacher>. Surely we can use reflection to look at the generic type argument on the List object that is passed in? Unfortunately, looking at the Method.ReturnType.IsGenericType returns false since the signature of the delegate we are declaring returns a non-generic IList.

Enter Expression<T>! The Expression<T> type is used to represent an expression tree, or an abstract syntax tree (AST). They are primarily used by LINQ providers to construct the necessary instructions from a lambda expression. For example, LINQ to SQL will convert a lambda expression into relevant SQL statements. For a good, short article on Expression<T>, check out this article on odetocode.com (Cheers Scott!).

Expression trees can be used to investigate almost any aspect of a lambda expression, including the actual type that is returned from the expression. To pass a lambda expression as Expression<T> instead of Func<T>, simply declare your function or property as Expression<Func<T>>. For example, in our case, we need to declare our property as:

        public Expression<Func<IList>> DataMember { get; set; }
This allows us to expose the generic type passed, if any
        public Type ListType
        {
            get
            {
                var member = (MemberExpression)DataMember.Body;
                if (member.Type.IsGenericType)
                    return member.Type.GetGenericArguments()[0];
                else
                    return typeof(object);
            }
        }
This will allow us to create new instances of the desired type using reflection in the BindingSource.AddingNew event handler, as well as do some generic formatting of grid columns based on the type, etc. More on the power of expression trees, including different expression types and some different applications soon!

Labels: , ,

1 Comments:

Blogger Paul said...

I like the simple example of combining the "Students" option with a function that retrieves students. You can simplify the code a little by replacing .Invoke() with simply ().

The Expression<T> example doesn't quite sit well with me though. Remind me to bring it up the next time we grab lunch!

8:30 AM  

Post a Comment

<< Home