For .NET 2.0, here's a nice bit of code I wrote that does exactly what you want, and works for any property on a Control:
private delegate void SetControlPropertyThreadSafeDelegate( Control control, string propertyName, object propertyValue); public static void SetControlPropertyThreadSafe( Control control, string propertyName, object propertyValue) { if (control.InvokeRequired) { control.Invoke(new SetControlPropertyThreadSafeDelegate (SetControlPropertyThreadSafe), new object[] { control, propertyName, propertyValue }); } else { control.GetType().InvokeMember( propertyName, BindingFlags.SetProperty, null, control, new object[] { propertyValue }); } }
Call it like this:
// thread-safe equivalent of // myLabel.Text = status; SetControlPropertyThreadSafe(myLabel, "Text", status);
If you're using .NET 3.0 or above, you could rewrite the above method as an extension method of the Control class, which would then simplify the call to:
myLabel.SetPropertyThreadSafe("Text", status);
UPDATE 05/10/2010:
For .NET 3.0 you should use this code:
private delegate void SetPropertyThreadSafeDelegate<TResult>( Control @this, Expression<Func<TResult>> property, TResult value); public static void SetPropertyThreadSafe<TResult>( this Control @this, Expression<Func<TResult>> property, TResult value) { var propertyInfo = (property.Body as MemberExpression).Member as PropertyInfo; if (propertyInfo == null || [email protected]().IsSubclassOf(propertyInfo.ReflectedType) || @this.GetType().GetProperty( propertyInfo.Name, propertyInfo.PropertyType) == null) { throw new ArgumentException("The lambda expression 'property' must reference a valid property on this Control."); } if (@this.InvokeRequired) { @this.Invoke(new SetPropertyThreadSafeDelegate<TResult> (SetPropertyThreadSafe), new object[] { @this, property, value }); } else { @this.GetType().InvokeMember( propertyInfo.Name, BindingFlags.SetProperty, null, @this, new object[] { value }); } }
which uses LINQ and lambda expressions to allow much cleaner, simpler and safer syntax:
// status has to be of type string or this will fail to compile myLabel.SetPropertyThreadSafe(() => myLabel.Text, status);
Not only is the property name now checked at compile time, the property's type is as well, so it's impossible to (for example) assign a string value to a boolean property, and hence cause a runtime exception.
Unfortunately this doesn't stop anyone from doing stupid things such as passing in another Control's property and value, so the following will happily compile:
myLabel.SetPropertyThreadSafe(() => aForm.ShowIcon, false);
Hence I added the runtime checks to ensure that the passed-in property does actually belong to the Control that the method's being called on. Not perfect, but still a lot better than the .NET 2.0 version.
If anyone has any further suggestions on how to improve this code for compile-time safety, please comment!