Thursday, April 4, 2013

Using Extensions to Add Behavior

I discussed .NET extensions in a previous article and provided some useful examples. I've lately considered other fine uses for this feature that promise to make code not only simpler, but more readable and more self-documented. That's the real value of extensions. You can write the same code without them and your application will work just as well. But by using extensions, you have the opportunity to make your code more easily maintained.

Here's a great example:

I was recently coding an application that required a context menu for a DataGridView which operated on the selected row. Simple enough? Well, not if you expect a right-click to select the row in addition to showing the context menu. As it turns out, this does not work that way in the WinForms DataGridView. To make it work, I had to select the row in code during the MouseUp even when a right-click occurred.


    this.dataGridView1.MouseUp += (o, m) =>
    {
        DataGridView.HitTestInfo hti = this.dataGridView1.HitTest(m.X, m.Y);
        if (m.Button == MouseButtons.Right && hti.RowIndex != -1)
            if (!grid.Rows[hti.RowIndex].Selected)
            {
                this.dataGridView1.ClearSelection();
                this.dataGridView1.Rows[hti.RowIndex].Selected = true;
            }
    };


That's not too bad, but it would be messy if I needed to do this a lot. 
It sure would have been nice if there were a property I could set for this behavior. Extensions won't let you create properties, but the simplicity of a property can be achieved. What I'd like to do, then, is to call a method like so:
 
this.fileListDataGridView.SelectRowOnRightClick();
 

That's must better. Well, I can easily accomplish this will an extension:


public static class DataGridViewBehaviors
{
    public static void SelectRowOnRightClick(this DataGridView grid)
    {
        grid.MouseUp += (a, m) =>
        {
            DataGridView.HitTestInfo hti = grid.HitTest(m.X, m.Y);
            if (m.Button == MouseButtons.Right && hti.RowIndex != -1)
                if (!grid.Rows[hti.RowIndex].Selected)
                {
                    grid.ClearSelection();
                    grid.Rows[hti.RowIndex].Selected = true;
                }
        };
    }
}


Notice that I chose to call the class containing this extension DataGridViewBehaviors. Although this is not all that different from extensions that act on an object, such as GetSelectedRows(), conceptually this is a little different. I'm using the extension to configure the behavior of an object - a behavior that I'll likely want for other objects of the same type elsewhere.

Moreover, I can make my code much more readable because the method name is clear and concise. Making code readable is an important part of good coding and essential for maintaining that code.