The Extension Method Pack
27 May 2010Since .NET 3.0 came out, I’ve been enjoying taking advantage of extension methods and the ability to create my own. The thing I’ve noticed is that a handful of them are useful to almost any application, above and beyond what Microsoft provides in System.Linq
. So over the last few days I took the time to gather these methods together, unit test them, and run them through FXCop to make a high-quality package ready to go in any application with a little re-namespacing.
I’ve broken each code sample into independent blocks wherein all necessary dependencies are contained, so you can take any extension method a la carte or you can get everything from the attached zip file. My solution was built in .NET 4.0 in Visual Studio 2010, but everything should work just fine in .NET 3.5 with Visual Studio 2008.
Also included in the zip file are my unit tests, which may help you understand usage of some of the more esoteric extensions, such as ChainGet, and XML comments for your IntelliSense and XML documentation generator.
Edit: The whole solution is now available on github!
Here’s the table of contents, so you can jump around more easily:
- IEnumerable
- ICollection
- IDictionary
- Object
- DependencyObject
IEnumerable
ForEach
is pretty straightforward. It mimics List<T>.ForEach
, but for all IEnumerable
, both generic and weakly typed.
public static void ForEach<T>( this IEnumerable<T> collection, Action<T> action) { if (collection == null) throw new ArgumentNullException("collection"); if (action == null) throw new ArgumentNullException("action"); foreach (var item in collection) action(item); }
public static void ForEach( this IEnumerable collection, Action<object> action) { if (collection == null) throw new ArgumentNullException("collection"); if (action == null) throw new ArgumentNullException("action"); foreach (var item in collection) action(item); }
Table of Contents{.more-link.alignright}
Append
and Prepend
simply take an item and return a a new IEnumerable<T>
with that item on the end or beginning, respectively. Prepend
is the equivalent of the [[cons]]
operation to a list.
public static IEnumerable<T> Append<T>( this IEnumerable<T> collection, T item) { if (collection == null) throw new ArgumentNullException("collection"); if (item == null) throw new ArgumentNullException("item"); foreach (var colItem in collection) yield return colItem; yield return item; }
public static IEnumerable<T> Prepend<T>( this IEnumerable<T> collection, T item) { if (collection == null) throw new ArgumentNullException("collection"); if (item == null) throw new ArgumentNullException("item"); yield return item; foreach (var colItem in collection) yield return colItem; }
Table of Contents{.more-link.alignright}
AsObservable
and AsHashSet
yield their respective data structures, but check to see if they are already what you want, saving valuable time when dealing with interfaces.
public static ObservableCollection<T> AsObservable<T>( this IEnumerable<T> collection) { if (collection == null) throw new ArgumentNullException("collection"); return collection as ObservableCollection<T> ?? new ObservableCollection<T>(collection); }
public static HashSet<T> AsHashSet<T>(this IEnumerable<T> collection) { if (collection == null) throw new ArgumentNullException("collection"); return collection as HashSet<T> ?? new HashSet<T>(collection); }
Table of Contents{.more-link.alignright}
[[Arg max|ArgMax]]
and ArgMin
are corollaries to Max
and Min
in the System.Linq
namespace, but return the item in the list that produced the highest value from Max
or least value from Min
, respectively.
public static T ArgMax<T, TValue>( this IEnumerable<T> collection, Func<T, TValue> function) where TValue : IComparable<TValue> { return ArgComp(collection, function, GreaterThan); } private static bool GreaterThan<T>(T first, T second) where T : IComparable<T> { return first.CompareTo(second) > 0; } public static T ArgMin<T, TValue>( this IEnumerable<T> collection, Func<T, TValue> function) where TValue : IComparable<TValue> { return ArgComp(collection, function, LessThan); } private static bool LessThan<T>(T first, T second) where T : IComparable<T> { return first.CompareTo(second) < 0; } private static T ArgComp<T, TValue>( IEnumerable<T> collection, Func<T, TValue> function, Func<TValue, TValue, bool> accept) where TValue : IComparable<TValue> { if (collection == null) throw new ArgumentNullException("collection"); if (function == null) throw new ArgumentNullException("function"); var isSet = false; var maxArg = default(T); var maxValue = default(TValue); foreach (var item in collection) { var value = function(item); if (!isSet || accept(value, maxValue)) { maxArg = item; maxValue = value; isSet = true; } } return maxArg; }
Table of Contents{.more-link.alignright}
ICollection
AddAll
imitates List<T>.AddRange
.
public static void AddAll<T>( this ICollection<T> collection, IEnumerable<T> additions) { if (collection == null) throw new ArgumentNullException("collection"); if (additions == null) throw new ArgumentNullException("additions"); if (collection.IsReadOnly) throw new InvalidOperationException("collection is read only"); foreach (var item in additions) collection.Add(item); }
Table of Contents{.more-link.alignright}
RemoveAll
imitates List<T>.RemoveAll
. A second overload allows you to specify the removals if you already have them.
public static IEnumerable<T> RemoveAll<T>( this ICollection<T> collection, Predicate<T> predicate) { if (collection == null) throw new ArgumentNullException("collection"); if (predicate == null) throw new ArgumentNullException("predicate"); if (collection.IsReadOnly) throw new InvalidOperationException("collection is read only"); // we can't possibly remove more than the entire list. var removals = new List<T>(collection.Count); // this is an O(n + m * k) operation where n is collection.Count, // m is removals.Count, and K is the removal operation time. Because // we know n >= m, this is an O(n + n * k) operation or just O(n * k). foreach (var item in collection) if (predicate(item)) removals.Add(item); foreach (var item in removals) collection.Remove(item); return removals; }
public static void RemoveAll<T>( this ICollection<T> collection, IEnumerable<T> removals) { if (collection == null) throw new ArgumentNullException("collection"); if (removals == null) throw new ArgumentNullException("removals"); if (collection.IsReadOnly) throw new InvalidOperationException("collection is read only"); foreach (var item in removals) collection.Remove(item); }
Table of Contents{.more-link.alignright}
IDictionary
All of these methods have to do with [[Hash table | hash tables]]. I use them pretty frequently and these methods are able to make life a lot easier. |
Add
and AddAll
insert the key and a new collection into the dictionary if the key doesn’t already exist and then adds the item or items to the collection.
public static void Add<TKey, TCol, TItem>( this IDictionary<TKey, TCol> dictionary, TKey key, TItem item) where TCol : ICollection<TItem>, new() { if (dictionary == null) throw new ArgumentNullException("dictionary"); if (key == null) throw new ArgumentNullException("key"); TCol col; if (dictionary.TryGetValue(key, out col)) { if (col.IsReadOnly) throw new InvalidOperationException("bucket is read only"); } else dictionary.Add(key, col = new TCol()); col.Add(item); }
public static void AddAll<TKey, TCol, TItem>( this IDictionary<TKey, TCol> dictionary, TKey key, IEnumerable<TItem> additions) where TCol : ICollection<TItem>, new() { if (dictionary == null) throw new ArgumentNullException("dictionary"); if (key == null) throw new ArgumentNullException("key"); if (additions == null) throw new ArgumentNullException("additions"); TCol col; if (!dictionary.TryGetValue(key, out col)) dictionary.Add(key, col = new TCol()); foreach (var item in additions) col.Add(item); }
Table of Contents{.more-link.alignright}
Remove
and RemoveAll
simply remove items from the collection associated with the specified key, if there is one. For the predicate overloads, you need to explicitly construct your Predicate<T>
delegate because the C# compiler has trouble doing the type inference.
public static void Remove<TKey, TCol, TItem>( this IDictionary<TKey, TCol> dictionary, TKey key, TItem item) where TCol : ICollection<TItem> { if (dictionary == null) throw new ArgumentNullException("dictionary"); if (key == null) throw new ArgumentNullException("key"); TCol col; if (dictionary.TryGetValue(key, out col)) col.Remove(item); }
public static void RemoveAll<TKey, TCol, TItem>( this IDictionary<TKey, TCol> dictionary, TKey key, IEnumerable<TItem> removals) where TCol : ICollection<TItem> { if (dictionary == null) throw new ArgumentNullException("dictionary"); if (key == null) throw new ArgumentNullException("key"); if (removals == null) throw new ArgumentNullException("removals"); TCol col; if (dictionary.TryGetValue(key, out col)) foreach (var item in removals) col.Remove(item); }
public static IEnumerable<TItem> RemoveAll<TKey, TCol, TItem>( this IDictionary<TKey, TCol> dictionary, TKey key, Predicate<TItem> predicate) where TCol : ICollection<TItem> { if (dictionary == null) throw new ArgumentNullException("dictionary"); if (key == null) throw new ArgumentNullException("key"); if (predicate == null) throw new ArgumentNullException("predicate"); var removals = new List<TItem>(); TCol col; if (dictionary.TryGetValue(key, out col)) { foreach (var item in col) if (predicate(item)) removals.Add(item); foreach (var item in removals) col.Remove(item); } return removals; } // Usage: Dictionary<int, List<int>> myDictionary; myDictionary.RemoveAll(4, new Predicate<int>(i => i < 42));
Table of Contents{.more-link.alignright}
RemoveAndClean
and RemoveAllAndClean
both remove items from the collection associated with the specified key and if the resulting collection is empty, they remove the key from the dictionary as well. These come in both predicate and list forms.
public static void RemoveAndClean<TKey, TCol, TItem>( this IDictionary<TKey, TCol> dictionary, TKey key, TItem item) where TCol : ICollection<TItem> { if (dictionary == null) throw new ArgumentNullException("dictionary"); if (key == null) throw new ArgumentNullException("key"); TCol col; if (dictionary.TryGetValue(key, out col)) { col.Remove(item); if (col.Count == 0) dictionary.Remove(key); } }
public static void RemoveAllAndClean<TKey, TCol, TItem>( this IDictionary<TKey, TCol> dictionary, TKey key, IEnumerable<TItem> removals) where TCol : ICollection<TItem> { if (dictionary == null) throw new ArgumentNullException("dictionary"); if (key == null) throw new ArgumentNullException("key"); if (removals == null) throw new ArgumentNullException("removals"); TCol col; if (dictionary.TryGetValue(key, out col)) { foreach (var item in removals) col.Remove(item); if (col.Count == 0) dictionary.Remove(key); } }
public static IEnumerable<TItem> RemoveAllAndClean<TKey, TCol, TItem>( this IDictionary<TKey, TCol> dictionary, TKey key, Predicate<TItem> predicate) where TCol : ICollection<TItem> { if (dictionary == null) throw new ArgumentNullException("dictionary"); if (key == null) throw new ArgumentNullException("key"); if (predicate == null) throw new ArgumentNullException("predicate"); var removals = new List<TItem>(); TCol col; if (dictionary.TryGetValue(key, out col)) { foreach (var item in col) if (predicate(item)) removals.Add(item); foreach (var item in removals) col.Remove(item); if (col.Count == 0) dictionary.Remove(key); } return removals; } // Usage: Dictionary<int, List<int>> myDictionary; myDictionary.RemoveAll(4, new Predicate<int>(i => i < 42));
Table of Contents{.more-link.alignright}
Clean
simply goes through all the keys and removes entries with empty collections.
public static void Clean<TKey, TCol>(this IDictionary<TKey, TCol> dictionary) where TCol : ICollection { if (dictionary == null) throw new ArgumentNullException("dictionary"); var keys = dictionary.Keys.ToList(); foreach (var key in keys) if (dictionary[key].Count == 0) dictionary.Remove(key); }
Table of Contents{.more-link.alignright}
Object
As casts an object to a specified type, executes an action with it, and returns whether or not the cast was successful.
public static bool As<T>(this object obj, Action<T> action) where T : class { if (obj == null) throw new ArgumentNullException("obj"); if (action == null) throw new ArgumentNullException("action"); var target = obj as T; if (target == null) return false; action(target); return true; }
Table of Contents{.more-link.alignright}
AsValueType
does the same thing as As
, but for value types.
public static bool AsValueType<T>(this object obj, Action<T> action) where T : struct { if (obj == null) throw new ArgumentNullException("obj"); if (action == null) throw new ArgumentNullException("action"); if (obj is T) { action((T)obj); return true; } return false; }
Table of Contents{.more-link.alignright}
ChainGet
attempts to resolve a chain of member accesses and returns the result or default(TValue)
. Since TValue
could be a value type, there is also an overload that has an out parameter indicating whether the value was obtained.
Be careful with this extension. Since it uses reflection, it’s a bit slow. For discrete usage, each call is under 1 ms, but if you use it in a loop with many items, the performance hit will become more tangible.
public static TValue ChainGet<TRoot, TValue>( this TRoot root, Expression<Func<TRoot, TValue>> getExpression) { bool success; return ChainGet(root, getExpression, out success); } public static TValue ChainGet<TRoot, TValue>( this TRoot root, Expression<Func<TRoot, TValue>> getExpression, out bool success) { // it's ok if root is null! if (getExpression == null) throw new ArgumentNullException("getExpression"); var members = new Stack<MemberAccessInfo>(); Expression expr = getExpression.Body; while (expr != null) { if (expr.NodeType == ExpressionType.Parameter) break; var memberExpr = expr as MemberExpression; if (memberExpr == null) throw new ArgumentException( "Given expression is not a member access chain.", "getExpression"); members.Push(new MemberAccessInfo(memberExpr.Member)); expr = memberExpr.Expression; } object node = root; foreach (var member in members) { if (node == null) { success = false; return default(TValue); } node = member.GetValue(node); } success = true; return (TValue)node; } private class MemberAccessInfo { private PropertyInfo _propertyInfo; private FieldInfo _fieldInfo; public MemberAccessInfo(MemberInfo info) { _propertyInfo = info as PropertyInfo; _fieldInfo = info as FieldInfo; } public object GetValue(object target) { if (_propertyInfo != null) return _propertyInfo.GetValue(target, null); else if (_fieldInfo != null) return _fieldInfo.GetValue(target); else throw new InvalidOperationException(); } } // Usage: var myValue = obj.ChainGet(o => o.MyProperty.MySubProperty.MySubSubProperty.MyValue);
Table of Contents{.more-link.alignright}
DependencyObject
These extensions work just like their non-safe counterparts on DependencyObject
, but will call Dispatcher.Invoke
to do operations if the current thread isn’t a UI thread.
public static object SafeGetValue( this DependencyObject obj, DependencyProperty dp) { if (obj == null) throw new ArgumentNullException("obj"); if (dp == null) throw new ArgumentNullException("dp"); if (obj.CheckAccess()) return obj.GetValue(dp); var self = new Func <DependencyObject, DependencyProperty, object> (SafeGetValue); return Dispatcher.Invoke(self, obj, dp); }
public static void SafeSetValue( this DependencyObject obj, DependencyProperty dp, object value) { if (obj == null) throw new ArgumentNullException("obj"); if (dp == null) throw new ArgumentNullException("dp"); if (obj.CheckAccess()) obj.SetValue(dp, value); else { var self = new Action <DependencyObject, DependencyProperty, object> (SafeSetValue); Dispatcher.Invoke(self, obj, dp, value); } }
public static void SafeSetValue( this DependencyObject obj, DependencyPropertyKey key, object value) { if (obj == null) throw new ArgumentNullException("obj"); if (key == null) throw new ArgumentNullException("key"); if (obj.CheckAccess()) obj.SetValue(key, value); else { var self = new Action <DependencyObject, DependencyPropertyKey, object> (SafeSetValue); Dispatcher.Invoke(self, obj, key, value); } }
Table of Contents{.more-link.alignright}