A better way to write extension method to invoke a control?

对着背影说爱祢 提交于 2019-12-06 04:55:14

First let me say that you may be overthinking this - short code is a great thing, but there's a point where it starts to be confusing for anyone attempting to read the code.

Now, your first suggestion:

button1.Invoke(Hide);

could work, if you make it:

button1.Invoke(button1.Hide); 

because otherwise the compiler cannot know, where to look for the method Hide(). It could even cause some weird behavior, for example if all of this code was in some derived class, like this:

class A : Control {
    public A() {
         Button button1=new Button();
         button1.Invoke(Hide);
    }
}

Now it would compile, but the Hide() method would be the Hide() method of whole control, not the button! The way to achive this is simply:

public static void Invoke(this Control c, Action action) {
    c.Invoke(action);
}

The latter way:

button1.Hide().Invoke();

would work even without adding extension methods, you just need to make it:

((Action)button1.Hide).Invoke();

This of course means the Hide() method gets invoked in the current thread, which is probably not what you want. So make it:

((Action)button1.Hide).Invoke(button1);
public static void Invoke(this Action action, Control c) {
    c.Invoke(action);
}

Sorry for long answer, hope it helps.

To build off other answers, I would put this into a separate extension class.

public static void Invoke<T>(this T c, Action<T> action) where T : Control
    {
        if (c.InvokeRequired)
            c.Invoke(new Action<T, Action<T>>(Invoke), new object[] { c, action });
        else
            action(c);
    }

This will prevent a TargetParameterCountException from being thrown when cross-threading.

To Call:

button1.Invoke(x => x.Hide());

You can use SynchronizationContext.Post or SynchronizationContext.Send to have the framework marshal the action to the UI thread, whether it is Windows Forms or WPF. The static SynchronizationContext.Current method will return an appropriate Synchronization context for your application type.

Post executes asynchronously while Send blocks until the action finishes.

The following code will hide a button asynchronously:

SynchronizationContext.Current.Post(_=>button1.Hide(),null);

I would go with:

public static void Invoke<T>(this T c, Action<T> action) where T : Control
{
    if (c.InvokeRequired)
        c.TopLevelControl.Invoke(action);
    else
        action(c);
}

and

button.Invoke(c => c.Hide());

It is the one that is the cleanest (you are given the button originally specified back to perform the action on) and safest (you don't have to specify button1 twice...it is given back to you as a parameter to your lambda). I believe this is elegant syntax.

It definitely can't be done like button1.Invoke(Hide); or button1.Hide.Invoke(); because of C# syntax restrictions.

But if you're willing to give up IntelliSense, you can make it a bit shorter. As a downside, some bugs which can usually be detected and fixed during compile-time (like typos or mismatch parameters) will become run-time errors. Sometimes it's acceptable, sometimes it's not.

Looking ahead, here is an example usage:

button1.Invoke("Hide");

or

button1.Invoke("ResumeLayout", true);

Solution:

internal static class ExtensionMethods
{
    internal static object Invoke<TControl>(this TControl control,
        string methodName, params object[] parameters)
        where TControl : Control
    {
        object result;

        if (control == null)
            throw new ArgumentNullException("control");

        if (string.IsNullOrEmpty(methodName))
            throw new ArgumentNullException("methodName");

        if (control.InvokeRequired)
            result = control.Invoke(new MethodInvoker(() => Invoke(control,
                methodName, parameters)));
        else
        {
            MethodInfo mi = null;

            if (parameters != null && parameters.Length > 0)
            {
                Type[] types = new Type[parameters.Length];
                for (int i = 0; i < parameters.Length; i++)
                {
                    if (parameters[i] != null)
                        types[i] = parameters[i].GetType();
                }

                mi = control.GetType().GetMethod(methodName,
                    BindingFlags.Instance | BindingFlags.Public,
                    null,  types, null);
            }
            else
                mi = control.GetType().GetMethod(methodName,
                    BindingFlags.Instance | BindingFlags.Public);

            if (mi == null)
                throw new InvalidOperationException(methodName);

            result = mi.Invoke(control, parameters);
        }

        return result;
    }
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!