# VBForums CodeBank > CodeBank - C# >  [Extension] Set control properties across threads

## NickThissen

[VB version here]


*Updated: includes methods and functions now as well.*


*Update:*
ForumAccount suggested that the code can be used for any type that implements ISynchronizeInvoke, not just Controls, see here. Since I don't have time to update all this code right now, if you need this change you can just change every Control reference to ISynchronizeInvoke instead.


-------

Hi,

Usually if you want to set a property of a control from another thread you will get an invalid cross-thread operation exception. To solve this you need to invoke a delegate on the same thread, which usually goes like this:

csharp Code:
using System;using System.Linq;using System.Linq.Expressions;using System.Reflection;using System.Windows.Forms; namespace ThreadSafeExtensions{    public static class Extensions    {        private delegate void SetThreadSafePropertyDelegate<T>(            Control control, Expression<Func<T>> property, T value);        private delegate T GetThreadSafePropertyDelegate<T>(Control control, Expression<Func<T>> property);        private delegate void InvokeThreadSafeMethodDelegate(Control control, Expression<Action> method);        private delegate T InvokeThreadSafeFunctionDelegate<T>(Control control, Expression<Func<T>> function);          /// <summary>        /// Sets the specified property of this control to the specified value safely across threads by invoking a delegate if necessary.        /// </summary>        /// <typeparam name="T">The type of the property. Can usually be inferred from usage.</typeparam>        /// <param name="control">The control to set the property on.</param>        /// <param name="property">The property to set as a lambda expression.</param>        /// <param name="value">The new value of the property.</param>        public static void SetThreadSafeProperty<T>(this Control control, Expression<Func<T>> property, T value)        {            if (control.InvokeRequired)            {                var del = new SetThreadSafePropertyDelegate<T>(SetThreadSafeProperty);                control.Invoke(del, control, property, value);            }            else            {                PropertyInfo propertyInfo = GetPropertyInfo(property);                if (propertyInfo != null)                {                    propertyInfo.SetValue(control, value, null);                }            }        }         /// <summary>        /// Gets the value of the specified property of this control safely across threads by invoking a delegate if necessary.        /// </summary>        /// <typeparam name="T">The type of the property. Can usually be inferred from usage.</typeparam>        /// <param name="control">The control to get the property from.</param>        /// <param name="property">The property to get the value from as a lambda expression.</param>        /// <returns>The value of the specified property.</returns>        public static T GetThreadSafeProperty<T>(this Control control, Expression<Func<T>> property)        {            if (control.InvokeRequired)            {                var del = new GetThreadSafePropertyDelegate<T>(GetThreadSafeProperty);                return (T) control.Invoke(del, control, property);            }            else            {                PropertyInfo propertyInfo = GetPropertyInfo(property);                if (propertyInfo != null)                {                    return (T) propertyInfo.GetValue(control, null);                }            }            return default(T);        }         /// <summary>        /// Invokes a method of this control safely across threads by invoking a delegate if necessary.        /// </summary>        /// <param name="control">The control to invoke the method on.</param>        /// <param name="method">The method to invoke as an expression.</param>        public static void InvokeThreadSafeMethod(this Control control, Expression<Action> method)        {            if (control.InvokeRequired)            {                var del = new InvokeThreadSafeMethodDelegate(InvokeThreadSafeMethod);                control.Invoke(del, control, method);            }            else            {                method.Compile().DynamicInvoke();            }        }         /// <summary>        /// Invokes a function of this control safely across threads by invoking a delegate if necessary.        /// </summary>        /// <param name="control">The control to invoke the function on.</param>        /// <param name="function">The function to invoke as an expression</param>        public static T InvokeThreadSafeFunction<T>(this Control control, Expression<Func<T>> function)        {            if (control.InvokeRequired)            {                var del = new InvokeThreadSafeFunctionDelegate<T>(InvokeThreadSafeFunction);                return (T) control.Invoke(del, control, function);            }            else            {                return (T) function.Compile().DynamicInvoke();            }        }                private static MemberInfo GetMemberInfo(Expression expression)        {            MemberExpression memberExpression;            LambdaExpression lambda = (LambdaExpression)expression;            if (lambda.Body is UnaryExpression)            {                UnaryExpression unaryExpression = (UnaryExpression)lambda.Body;                memberExpression = (MemberExpression)unaryExpression.Operand;            }            else            {                memberExpression = (MemberExpression)lambda.Body;            }             return memberExpression.Member;        }         private static PropertyInfo GetPropertyInfo(Expression expression)        {            MemberInfo memberInfo = GetMemberInfo(expression);            if (memberInfo.MemberType == MemberTypes.Property)            {                return (PropertyInfo)memberInfo;            }            return null;        }    }}

This looks complicated but usage is pretty easy. You'll need to pass the property as an lambda expression (which turns out to something like "*() => <property here>*"), as well as the value you want to set it to. Even though the extension method takes a type parameter, it can usually be inferred from the objects you pass (the property lambda and the value) so you won't need to supply it.

Usage examples:

csharp Code:
// Set label textlabel1.SetThreadSafeProperty(() => label1.Text, "test text"); // Disable buttonbutton1.SetThreadSafeProperty(() => button1.Enabled, false); // Get TextBox textstring txt = textBox1.GetThreadSafeProperty(() => textBox1.Text); // Invoke some methodcustomControl1.InvokeThreadSafeMethod(() => customControl1.SomeMethod("a", 3, 14.3)); // Invoke some functionvar x = customControl2.InvokeThreadSafeFunction(() => customControl2.SomeFunction(y))
I'm not that much into threading yet, but it seems to work fine for a few little tests I've done.

The only drawback is that it uses Reflection which can be a little slow, but it's only one call and I think it should be fine.

I'm open to suggestions! One thing I don't much like is that it seems a little redundant to use the control name twice (first to call the extension method on, second to pass the property you want to get/set), but the only way I can think of to get around that is to pass the property name as a string, and I don't like that approach at all as it's error prone (no compile errors if you miss-spell it)...


Enjoy.

----------


## NickThissen

I've updated the code so that you can now also call methods and functions safely across threads; see the updated usage example code.

----------


## NickThissen

*Update:*
ForumAccount suggested that the code can be used for any type that implements ISynchronizeInvoke, not just Controls, see here. Since I don't have time to update all this code right now, if you need this change you can just change every Control reference to ISynchronizeInvoke instead.

----------


## weirddemon

Nick,

I'm having trouble with .GetThreadSafeproperty. Here's the error:



And this is how I'm using it:


c# Code:
foreach (ListViewItem lvi in this.lvwUserRightsMapping.GetThreadSafeProperty(() => this.lvwUserRightsMapping.Items))
                {
                    this.lbLoadingSequence.InvokeThreadSafeMethod(() => this.lbLoadingSequence.Items.Add("Loading Sequence: " + lvi.Index.ToString()));
                }

It's occuring on .Items.

Any ideas?

----------


## NickThissen

I'm not sure so quickly what is happening but it seems like a rather complicated mess. My extensions were mainly used to access a simple property without having to worry about cross-thread issues, basically it saves you having to write all the "if invoke required then invoke" code. I can imagine it will go wrong quickly if you start using it for properties returning collections and then modifying collections in a loop. I'm sure in your case it would be easier to handle the cross-threading issues as you normally would (eg, don't use my extensions but write the appropriate code) instead of trying to adjust my code to make it work in such complicated circumstances. I may be wrong though  :Smilie:

----------

