Extension Methods And Type Inference In Action
I make extensive use of extension methods, either to make classes small and focused or to improve readability.
While porting a .NET 1.1 WinForms application to C# 3.0, I found lots of code like this:
delegate int Int32DelegateStringBoolean(string text, bool flag);void DoStuff() { // ...
<span style="color: blue">var </span><font color="#000000">x = (</font><span style="color: blue">int</span><font color="#000000">)</font></span><span style="color: blue">this</span>.Invoke(<span style="color: blue">new </span><span style="color: #2b91af">Int32DelegateStringBoolean</span>(GetStuff), <span style="color: blue">new object</span>[] { <span style="color: #a31515">"some text"</span>, <span style="color: blue">true </span>}); <span style="color: green">// ...
}
int GetStuff(string text, bool flag) { // ... }
.NET 2.0 introduced a nicer API and it became possible to write the code calling Invoke like this:
var x = (int)this.Invoke(new Int32DelegateStringBoolean(GetStuff), "some text", true);
But it’s still not strongly typed enough to my taste and the compiler can’t verify if any mistake was made. This will still be valid code at compile time that will break at run time:
var x = (long)this.Invoke(new Int32DelegateStringBoolean(GetStuff), "some text", 10M, 5);
To make the code safer and more readable, I decided to create extension methods to extend the Control class and provide strongly type Invoke methods.
Instead of defining custom delegates for each need, I used the Action and Func delegates exiting in the framework and wrote extension methods like this:
public static TResult InvokeFunc<T1, T2, TResult>(this Control control, Func<T1, T2, TResult> func, T1 param1, T2 param2) { return (TResult)(control.Invoke(func, param1, param2)); }
Now I can replace the call to Invoke with this call:
var x = this.InvokeFunc<string, bool, int>(new Func<string, bool, int>(GetStuff), "some text", true);
And the compiler is now able to match the type of the delegate, the parameters and the return value.
Starting with the C# 2.0 compiler, there is no need to write the delegate instantiation if the compiler can infer the type of the delegate, which makes the code even simpler:
var x = this.InvokeFunc<string, bool, int>(GetStuff, "some text", true);
The C# compiler is even capable of inferring the type parameters of the InvokeFunc method making the code even smaller and more readable:
var x = this.InvokeFunc(GetStuff, "some text", true);
A lot better than what we started with, isn’t it?
So far, I’ve implemented these:
/// <summary> /// Provides extended functionality to <see cref="System.Windows.Forms.Control"/>. /// </summary> public static class ControlExtensions { /// <summary> /// Executes the specified function (<paramref name="func"/>) on the thread that owns the control's underlying window handle. /// </summary> /// <typeparam name="TResult">The type of the result.</typeparam> /// <param name="control">The control to invoke the function on.</param> /// <param name="func">The function to invoke.</param> /// <returns>A <typeparamref name="TResult"/> that contains the return value from the function (<paramref name="func"/>) being invoked.</returns> public static TResult InvokeFunc<TResult>(this Control control, Func<TResult> func) { return (TResult)(control.Invoke(func)); } /// <summary> /// Executes the specified function (<paramref name="func"/>) on the thread that owns the control's underlying window handle. /// </summary> /// <typeparam name="T1">The type of the parameter of the function.</typeparam> /// <typeparam name="TResult">The type of the result.</typeparam> /// <param name="control">The control to invoke the function on.</param> /// <param name="func">The function to invoke.</param> /// <param name="param1">The parameter of the action.</param> /// <returns>A <typeparamref name="TResult"/> that contains the return value from the function (<paramref name="func"/>) being invoked.</returns> public static TResult InvokeFunc<T1, TResult>(this Control control, Func<T1, TResult> func, T1 param1) { return (TResult)(control.Invoke(func, param1)); } /// <summary> /// Executes the specified function (<paramref name="func"/>) on the thread that owns the control's underlying window handle. /// </summary> /// <typeparam name="T1">The type of the first parameter of the function.</typeparam> /// <typeparam name="T2">The type of the second parameter of the function.</typeparam> /// <typeparam name="TResult">The type of the result.</typeparam> /// <param name="control">The control to invoke the function on.</param> /// <param name="func">The function to invoke.</param> /// <param name="param1">The first parameter of the function.</param> /// <param name="param2">The second parameter of the function.</param> /// <returns>A <typeparamref name="TResult"/> that contains the return value from the function (<paramref name="func"/>) being invoked.</returns> public static TResult InvokeFunc<T1, T2, TResult>(this Control control, Func<T1, T2, TResult> func, T1 param1, T2 param2) { return (TResult)(control.Invoke(func, param1, param2)); } /// <summary> /// Executes the specified function (<paramref name="func"/>) on the thread that owns the control's underlying window handle. /// </summary> /// <typeparam name="T1">The type of the first parameter of the function.</typeparam> /// <typeparam name="T2">The type of the second parameter of the function.</typeparam> /// <typeparam name="T3">The type of the third parameter of the function.</typeparam> /// <typeparam name="TResult">The type of the result.</typeparam> /// <param name="control">The control to invoke the function on.</param> /// <param name="func">The function to invoke.</param> /// <param name="param1">The first parameter of the function.</param> /// <param name="param2">The second parameter of the function.</param> /// <param name="param3">The third parameter of the function.</param> /// <returns>A <typeparamref name="TResult"/> that contains the return value from the function (<paramref name="func"/>) being invoked.</returns> public static TResult InvokeFunc<T1, T2, T3, TResult>(this Control control, Func<T1, T2, T3, TResult> func, T1 param1, T2 param2, T3 param3) { return (TResult)(control.Invoke(func, param1, param2, param3)); } /// <summary> /// Executes the specified function (<paramref name="func"/>) on the thread that owns the control's underlying window handle. /// </summary> /// <typeparam name="T1">The type of the first parameter of the function.</typeparam> /// <typeparam name="T2">The type of the second parameter of the function.</typeparam> /// <typeparam name="T3">The type of the third parameter of the function.</typeparam> /// <typeparam name="T4">The type of the forth parameter of the function.</typeparam> /// <typeparam name="TResult">The type of the result.</typeparam> /// <param name="control">The control to invoke the function on.</param> /// <param name="func">The function to invoke.</param> /// <param name="param1">The first parameter of the function.</param> /// <param name="param2">The second parameter of the function.</param> /// <param name="param3">The third parameter of the function.</param> /// <param name="param4">The forth parameter of the function.</param> /// <returns>A <typeparamref name="TResult"/> that contains the return value from the function (<paramref name="func"/>) being invoked.</returns> public static TResult InvokeFunc<T1, T2, T3, T4, TResult>(this Control control, Func<T1, T2, T3, T4, TResult> func, T1 param1, T2 param2, T3 param3, T4 param4) { return (TResult)(control.Invoke(func, param1, param2, param3, param4)); } /// <summary> /// Executes the specified action (<paramref name="action"/>) on the thread that owns the control's underlying window handle. /// </summary> /// <param name="control">The control to invoke the action on.</param> /// <param name="action">The action to invoke.</param> public static void InvokeAction(this Control control, Action action) { control.Invoke(action); } /// <summary> /// Executes the specified action (<paramref name="action"/>) on the thread that owns the control's underlying window handle. /// </summary> /// <typeparam name="T">The type of the parameter.</typeparam> /// <param name="control">The control to invoke the action on.</param> /// <param name="action">The action to invoke.</param> /// <param name="param">The parameter of the action.</param> public static void InvokeAction<T>(this Control control, Action<T> action, T param) { control.Invoke(action, param); } /// <summary> /// Executes the specified action (<paramref name="action"/>) on the thread that owns the control's underlying window handle. /// </summary> /// <typeparam name="T1">The type of the first parameter of the action.</typeparam> /// <typeparam name="T2">The type of the second parameter of the action.</typeparam> /// <param name="control">The control to invoke the action on.</param> /// <param name="action">The action to invoke.</param> /// <param name="param1">The first parameter of the action.</param> /// <param name="param2">The second parameter of the action.</param> public static void InvokeAction<T1, T2>(this Control control, Action<T1, T2> action, T1 param1, T2 param2) { control.Invoke(action, param1, param2); } /// <summary> /// Executes the specified action (<paramref name="action"/>) on the thread that owns the control's underlying window handle. /// </summary> /// <typeparam name="T1">The type of the first parameter of the action.</typeparam> /// <typeparam name="T2">The type of the second parameter of the action.</typeparam> /// <typeparam name="T3">The type of the third parameter of the action.</typeparam> /// <param name="control">The control to invoke the action on.</param> /// <param name="action">The action to invoke.</param> /// <param name="param1">The first parameter of the action.</param> /// <param name="param2">The second parameter of the action.</param> /// <param name="param3">The third parameter of the action.</param> public static void InvokeAction<T1, T2, T3>(this Control control, Action<T1, T2, T3> action, T1 param1, T2 param2, T3 param3) { control.Invoke(action, param1, param2, param3); } /// <summary> /// Executes the specified action (<paramref name="action"/>) on the thread that owns the control's underlying window handle. /// </summary> /// <typeparam name="T1">The type of the first parameter of the action.</typeparam> /// <typeparam name="T2">The type of the second parameter of the action.</typeparam> /// <typeparam name="T3">The type of the third parameter of the action.</typeparam> /// <typeparam name="T4">The type of the forth parameter of the action.</typeparam> /// <param name="control">The control to invoke the action on.</param> /// <param name="action">The action to invoke.</param> /// <param name="param1">The first parameter of the action.</param> /// <param name="param2">The second parameter of the action.</param> /// <param name="param3">The third parameter of the action.</param> /// <param name="param4">The forth parameter of the action.</param> public static void InvokeAction<T1, T2, T3, T4>(this Control control, Action<T1, T2, T3, T4> action, T1 param1, T2 param2, T3 param3, T4 param4) { control.Invoke(action, param1, param2, param3, param4); } /// <summary> /// Executes the specified function (<paramref name="func"/>) on the thread that owns the control's underlying window handle. /// </summary> /// <typeparam name="TResult">The type of the result.</typeparam> /// <param name="control">The control to BeginInvoke the function on.</param> /// <param name="func">The function to BeginInvoke.</param> /// <returns>An System.IAsyncResult that represents the result of the operation.</returns> public static IAsyncResult BeginInvokeFunc<TResult>(this Control control, Func<TResult> func) { return control.BeginInvoke(func); } /// <summary> /// Executes the specified function (<paramref name="func"/>) on the thread that owns the control's underlying window handle. /// </summary> /// <typeparam name="T1">The type of the parameter of the function.</typeparam> /// <typeparam name="TResult">The type of the result.</typeparam> /// <param name="control">The control to BeginInvoke the function on.</param> /// <param name="func">The function to BeginInvoke.</param> /// <param name="param1">The parameter of the action.</param> /// <returns>An System.IAsyncResult that represents the result of the operation.</returns> public static IAsyncResult BeginInvokeFunc<T1, TResult>(this Control control, Func<T1, TResult> func, T1 param1) { return control.BeginInvoke(func, param1); } /// <summary> /// Executes the specified function (<paramref name="func"/>) on the thread that owns the control's underlying window handle. /// </summary> /// <typeparam name="T1">The type of the first parameter of the function.</typeparam> /// <typeparam name="T2">The type of the second parameter of the function.</typeparam> /// <typeparam name="TResult">The type of the result.</typeparam> /// <param name="control">The control to BeginInvoke the function on.</param> /// <param name="func">The function to BeginInvoke.</param> /// <param name="param1">The first parameter of the function.</param> /// <param name="param2">The second parameter of the function.</param> /// <returns>An System.IAsyncResult that represents the result of the operation.</returns> public static IAsyncResult BeginInvokeFunc<T1, T2, TResult>(this Control control, Func<T1, T2, TResult> func, T1 param1, T2 param2) { return control.BeginInvoke(func, param1, param2); } /// <summary> /// Executes the specified function (<paramref name="func"/>) on the thread that owns the control's underlying window handle. /// </summary> /// <typeparam name="T1">The type of the first parameter of the function.</typeparam> /// <typeparam name="T2">The type of the second parameter of the function.</typeparam> /// <typeparam name="T3">The type of the third parameter of the function.</typeparam> /// <typeparam name="TResult">The type of the result.</typeparam> /// <param name="control">The control to BeginInvoke the function on.</param> /// <param name="func">The function to BeginInvoke.</param> /// <param name="param1">The first parameter of the function.</param> /// <param name="param2">The second parameter of the function.</param> /// <param name="param3">The third parameter of the function.</param> /// <returns>An System.IAsyncResult that represents the result of the operation.</returns> public static IAsyncResult BeginInvokeFunc<T1, T2, T3, TResult>(this Control control, Func<T1, T2, T3, TResult> func, T1 param1, T2 param2, T3 param3) { return control.BeginInvoke(func, param1, param2, param3); } /// <summary> /// Executes the specified function (<paramref name="func"/>) on the thread that owns the control's underlying window handle. /// </summary> /// <typeparam name="T1">The type of the first parameter of the function.</typeparam> /// <typeparam name="T2">The type of the second parameter of the function.</typeparam> /// <typeparam name="T3">The type of the third parameter of the function.</typeparam> /// <typeparam name="T4">The type of the forth parameter of the function.</typeparam> /// <typeparam name="TResult">The type of the result.</typeparam> /// <param name="control">The control to BeginInvoke the function on.</param> /// <param name="func">The function to BeginInvoke.</param> /// <param name="param1">The first parameter of the function.</param> /// <param name="param2">The second parameter of the function.</param> /// <param name="param3">The third parameter of the function.</param> /// <param name="param4">The forth parameter of the function.</param> /// <returns>An System.IAsyncResult that represents the result of the operation.</returns> public static IAsyncResult BeginInvokeFunc<T1, T2, T3, T4, TResult>(this Control control, Func<T1, T2, T3, T4, TResult> func, T1 param1, T2 param2, T3 param3, T4 param4) { return control.BeginInvoke(func, param1, param2, param3, param4); } /// <summary> /// Executes the specified action (<paramref name="action"/>) on the thread that owns the control's underlying window handle. /// </summary> /// <param name="control">The control to BeginInvoke the action on.</param> /// <param name="action">The action to BeginInvoke.</param> /// <returns>An System.IAsyncResult that represents the result of the operation.</returns> public static IAsyncResult BeginInvokeAction(this Control control, Action action) { return control.BeginInvoke(action); } /// <summary> /// Executes the specified action (<paramref name="action"/>) on the thread that owns the control's underlying window handle. /// </summary> /// <typeparam name="T">The type of the parameter.</typeparam> /// <param name="control">The control to BeginInvoke the action on.</param> /// <param name="action">The action to BeginInvoke.</param> /// <param name="param">The parameter of the action.</param> /// <returns>An System.IAsyncResult that represents the result of the operation.</returns> public static IAsyncResult BeginInvokeAction<T>(this Control control, Action<T> action, T param) { return control.BeginInvoke(action, param); } /// <summary> /// Executes the specified action (<paramref name="action"/>) on the thread that owns the control's underlying window handle. /// </summary> /// <typeparam name="T1">The type of the first parameter of the action.</typeparam> /// <typeparam name="T2">The type of the second parameter of the action.</typeparam> /// <param name="control">The control to BeginInvoke the action on.</param> /// <param name="action">The action to BeginInvoke.</param> /// <param name="param1">The first parameter of the action.</param> /// <param name="param2">The second parameter of the action.</param> /// <returns>An System.IAsyncResult that represents the result of the operation.</returns> public static IAsyncResult BeginInvokeAction<T1, T2>(this Control control, Action<T1, T2> action, T1 param1, T2 param2) { return control.BeginInvoke(action, param1, param2); } /// <summary> /// Executes the specified action (<paramref name="action"/>) on the thread that owns the control's underlying window handle. /// </summary> /// <typeparam name="T1">The type of the first parameter of the action.</typeparam> /// <typeparam name="T2">The type of the second parameter of the action.</typeparam> /// <typeparam name="T3">The type of the third parameter of the action.</typeparam> /// <param name="control">The control to BeginInvoke the action on.</param> /// <param name="action">The action to BeginInvoke.</param> /// <param name="param1">The first parameter of the action.</param> /// <param name="param2">The second parameter of the action.</param> /// <param name="param3">The third parameter of the action.</param> /// <returns>An System.IAsyncResult that represents the result of the operation.</returns> public static IAsyncResult BeginInvokeAction<T1, T2, T3>(this Control control, Action<T1, T2, T3> action, T1 param1, T2 param2, T3 param3) { return control.BeginInvoke(action, param1, param2, param3); } /// <summary> /// Executes the specified action (<paramref name="action"/>) on the thread that owns the control's underlying window handle. /// </summary> /// <typeparam name="T1">The type of the first parameter of the action.</typeparam> /// <typeparam name="T2">The type of the second parameter of the action.</typeparam> /// <typeparam name="T3">The type of the third parameter of the action.</typeparam> /// <typeparam name="T4">The type of the forth parameter of the action.</typeparam> /// <param name="control">The control to BeginInvoke the action on.</param> /// <param name="action">The action to BeginInvoke.</param> /// <param name="param1">The first parameter of the action.</param> /// <param name="param2">The second parameter of the action.</param> /// <param name="param3">The third parameter of the action.</param> /// <param name="param4">The forth parameter of the action.</param> /// <returns>An System.IAsyncResult that represents the result of the operation.</returns> public static IAsyncResult BeginInvokeAction<T1, T2, T3, T4>(this Control control, Action<T1, T2, T3, T4> action, T1 param1, T2 param2, T3 param3, T4 param4) { return control.BeginInvoke(action, param1, param2, param3, param4); } }
Use them if you need to.