Wednesday, November 23, 2005

Making GUI Thread Programming in C# easier - take 3

In my first post on this subject I referred to John Wood’s SafeInvokeHelper. I’ve been using it a lot lately and grown to love it...

I’ve decided to add to it a few features:

  1. In order to avoid having to know (or test using InvokeRequired) whether Invoke should be used or not, I’ve added a mechanism that checks InvokeRequired. If Invoke is not required, the method is called directly (without Invoke), which dramatically improves performance.
  2. In some cases you may want to call a method that does not belong to a Control object using Invoke. One example is when you want to alter the ListView.Items collection or one of the ListViewItem objects in it. Both the collection and the ListViewItem are not descendent of Control, hence do not support Invoke. However, it is pretty obvious that altering these objects has an impact on the UI and thus must be done through the UI thread (imagine changing a ListViewItem.BackColor property). So I’ve added an overload to SafeInvokeHelper that lets you specify the control on which Invoke should be called and the object of the method we actually want to execute.

For more details about the SafeInvokeHelper, please read John’s post.

Thank you John!

P.S: Note that the current implementation does not support overloaded methods.

using System;

using System.Collections;

using System.Reflection;

using System.Reflection.Emit;

using System.Windows.Forms;

...

public class SafeInvokeHelper

{

private static readonly ModuleBuilder builder;

private static readonly AssemblyBuilder myAsmBuilder;

private static readonly Hashtable methodLookup;

static SafeInvokeHelper()

{

AssemblyName name = new AssemblyName();

name.Name = "temp";

myAsmBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(name, AssemblyBuilderAccess.Run);

builder = myAsmBuilder.DefineDynamicModule("TempModule");

methodLookup = new Hashtable();

}

public static object Invoke(Control obj, string methodName, params object[] paramValues)

{

return Invoke(obj, obj, methodName, paramValues);

}

public static int counter = 0;

public static object Invoke(Control control, object obj, string methodName, params object[] paramValues)

{

if (!control.InvokeRequired)

{

if (methodName.StartsWith("set_"))

{

string propertyName = methodName.Substring(4);

return obj.GetType().InvokeMember(propertyName, BindingFlags.Instance BindingFlags.NonPublic

BindingFlags.Public BindingFlags.SetProperty BindingFlags.Static,

null, obj, paramValues);

}

else

{

return obj.GetType().InvokeMember(methodName, BindingFlags.Instance BindingFlags.InvokeMethod

BindingFlags.NonPublic BindingFlags.Public BindingFlags.Static,

null, obj, paramValues);

}

}

else

{

counter++;

Delegate del = null;

string key = obj.GetType().Name + "." + methodName;

if (paramValues != null)

{

foreach (object o in paramValues)

{

key += "." + o.GetType().Name;

}

}

if (methodLookup.Contains(key))

del = (Delegate) methodLookup[key];

else

{

Type[] paramList = new Type[obj.GetType().GetMethod(methodName).GetParameters().Length];

int n = 0;

foreach (ParameterInfo pi in obj.GetType().GetMethod(methodName).GetParameters()) paramList[n++] = pi.ParameterType;

TypeBuilder typeB = builder.DefineType("Del_" + obj.GetType().Name + "_" + methodName, TypeAttributes.Class TypeAttributes.AutoLayout TypeAttributes.Public TypeAttributes.Sealed, typeof (MulticastDelegate), PackingSize.Unspecified);

ConstructorBuilder conB = typeB.DefineConstructor(MethodAttributes.HideBySig MethodAttributes.SpecialName MethodAttributes.RTSpecialName, CallingConventions.Standard, new Type[] {typeof (object), typeof (IntPtr)});

conB.SetImplementationFlags(MethodImplAttributes.Runtime);

MethodBuilder mb = typeB.DefineMethod("Invoke", MethodAttributes.Public MethodAttributes.Virtual MethodAttributes.HideBySig, obj.GetType().GetMethod(methodName).ReturnType, paramList);

mb.SetImplementationFlags(MethodImplAttributes.Runtime);

Type tp = typeB.CreateType();

MethodInfo mi = obj.GetType().GetMethod(methodName);

del = MulticastDelegate.CreateDelegate(tp, obj, methodName);

methodLookup.Add(key, del);

}

return control.Invoke(del, paramValues);

}

}

}

No comments: