Extension methods enable you to "add" methods to existing types without creating a new derived type, recompiling, or otherwise modifying the original type. Extension methods are a special kind of static method, but they are called as if they were instance methods on the extended type.
The main reason to use extensions is to make code more readable and to reuse logic, especially when dealing with classes that you cannot change. For example, consider the following snippet for the get and set accessors of a ComboBox in a Windows Form application:
public List<StateDataView> StateList
{get
{
List<StateDataView> states = (List<StateDataView>)this.statesComboBox.DataSource;
states.RemoveAt(0);
return states;
}
set
{
List<StateDataView> states = new List<StateDataView>(value);
states.Insert(0, new StateDataView() { Code = "" });
this.statesComboBox.DataSource = states;
}
}
public StateDataView SelectedState
{
get
{
if (this.statesComboBox.SelectedIndex == 0) return null;
return (StateDataView)this.statesComboBox.SelectedItem;
}
}
In the Set accessor, I did a little trick to make sure there was a blank entry in the list. This would be useful when created a lookup form that has a variety of options that may not always be used. When returning the selected object in the Get accessor, I have to handle the case when the blank entry is selected, in which case I want to return a null. This code isn't too bad, but imagine how ugly it would look if I had a lot of ComboBoxes. Wouldn't it be great if the could could be written like this?
public List<StateDataView> StateList
{
get { return this.statesComboBox.GetDataSourceWithoutBlank<StateDataView>(); }
set { this.statesComboBox.SetDataSourceWithBlank<StateDataView>(value); }
}
public StateDataView SelectedState
{
get { return this.statesComboBox.GetSelectedItemUnlessFirst<StateDataView>(); }
}
Notice that there are three methods I am calling on the ComboBox control that do not exist in the framework. These are my own extension methods. It should be fairly clear by their names what they do.
If I only had one or two cases like this, I wouldn't bother with the extension method. However, I find this particular scenario to be quite common. Let's look at the extension methods that make it happen. They exist in a class I created specifically to extend the Windows Form ComboBox:
public static class ComboBoxExtensions
{
public static void SetDataSourceWithBlank<T>(this ComboBox combo, List<T> list)
where T : new()
{
List<T> newlist = new List<T>(list);
newlist.Insert(0, new T());
combo.DataSource = newlist;
}
public static List<T> GetDataSourceWithoutBlank<T>(this ComboBox combo)
{
List<T> list = (List<T>)combo.DataSource;
list.RemoveAt(0);
return list;
}
public static T GetSelectedItemUnlessFirst<T>(this ComboBox combo)
where T : class
{
if (combo.SelectedIndex == 0) return null;
return (T)combo.SelectedItem;
}
}
There are a few important characteristics of extension methods:
- They must exist in a static class.
- They must themselves be static.
- The first parameter must be prefixed with the keywork this and must be the type being extended.
- Since I needed to create a new instance of an object in the SetDataSourceWithBlank method, I had to use the new() type constraint.
- Since I needed to return a null in the GetSelectedItemUnlessFirst method, I had to use the class constraint.
Some Useful Extensions
For the remainder of this article, I thought I'd share a few extensions that I've created that you might find useful.GridView.SelectWhere<T>
Description: This method allows you to select rows in a DataGridView based on the properties of the bound object. I typically use this to select a specific row, but it could also be used to select a set or rows.Usage: grid.SelectWhere<type>(x => x.property == value);
public static void SelectWhere<T>(this
DataGridView grid,
Expression<Func<T,bool>>
testexpression)
{grid.ClearSelection();
Func<T,bool> test = testexpression.Compile();
foreach (DataGridViewRow row in grid.Rows)
{
T item = (T)row.DataBoundItem;
if (test(item))
{
row.Selected = true;
}
}
}
IEnumerable<T>.Distinct
Description: Gets a distinct list of objects from an IEnumerable based on a filter expression. In this case, the key must be a nullable integer, but Icould easily create different overloads for different types.Usage: list.Distinct(x => x.filterproperty);
public static IEnumerable<T> Distinct<T>(this IEnumerable<T>
source,
Expression<Func<T,int?>>
key)
{List<T> items = new List<T>();
Func<T,int?> keyprop = key.Compile();
foreach (T sourceitem in source.ToList())
{
int? id = keyprop(sourceitem);
if (items.Find(i => keyprop(i) == id) == null)
items.Add(sourceitem);
}
return items.AsEnumerable();
}
Nullable<T>.Coalesce
Description: Just like the T-SQL equivalent, this method gives you an alternative value to return when a nullable type is null, e.g. if you want a nullable int to return a -1 when it's null.Usage: value.Coalesce(x);
public static T
Coalesce<T>(this Nullable<T>
value, T nullvalue)
{
if (value.HasValue) return
value.Value;
return nullvalue;
}
No comments:
Post a Comment