编写扩展方法来调用控件的更好方法是什么?

时间:2012-12-24 10:10:27

标签: c# c#-4.0 lambda type-inference method-invocation

我有这个通用函数来调用WinForm控件:

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

我想通过采取更严厉的限制措施来防止无意义的事情变得更好,可能就像:

button1.Invoke(() => list.Add(1));

还可以进行多余的输入:

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

因为我们已经指定thisbutton1

所以我做到了:

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

现在我必须打电话,

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

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

现在我觉得即便如此,还需要打字。如果我指定thisbutton1,那么在lambda表达式中我不想再次指定虚拟变量c来告诉操作的位置。无论如何我可以再缩短一下吗?也许就像

button1.Invoke(Hide);

button1.Hide.Invoke();
C#中的

左右?

5 个答案:

答案 0 :(得分:3)

为了建立其他答案,我会把它放到一个单独的扩展类中。

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);
    }

这样可以防止在交叉线程时抛出TargetParameterCountException

致电:

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

答案 1 :(得分:2)

首先让我说你可能会过度思考这个问题 - 短代码是件好事,但是对于任何试图阅读代码的人而言,它都会让人感到困惑。

现在,你的第一个建议是:

button1.Invoke(Hide);

可以工作,如果你做到了:

button1.Invoke(button1.Hide); 

因为否则编译器无法知道,在哪里查找方法Hide()。它甚至可能导致一些奇怪的行为,例如,如果所有这些代码都在某个派生类中,如下所示:

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

现在它会编译,但Hide()方法将是整个控件的Hide()方法,而不是按钮! 实现这一目标的方法很简单:

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

后一种方式:

button1.Hide().Invoke();
即使没有添加扩展方法,

也可以工作,你只需要创建它:

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

这当然意味着在当前线程中调用Hide()方法,这可能不是您想要的。所以吧:

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

很抱歉得到长时间的回答,希望有所帮助。

答案 2 :(得分:2)

您可以使用SynchronizationContext.PostSynchronizationContext.Send让框架将操作封送到UI线程,无论是Windows窗体还是WPF。静态SynchronizationContext.Current方法将为您的应用程序类型返回适当的同步上下文。

Post发送阻止时异步执行,直到操作完成。

以下代码将异步隐藏按钮:

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

答案 3 :(得分:2)

我会选择:

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

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

它是最干净的(你最初指定的按钮执行操作)和最安全的(你不必指定button1两次......它被给予回你作为你的lambda的参数)。我相信这是优雅的语法。

答案 4 :(得分:1)

由于C#语法限制,它绝对不能像button1.Invoke(Hide);button1.Hide.Invoke();那样完成。

但是如果你愿意放弃IntelliSense,你可以缩短它。作为缺点,在编译时通常可以检测和修复的一些错误(如拼写错误或不匹配参数)将成为运行时错误。有时它是可以接受的,有时它不是。

展望未来,这是一个示例用法

button1.Invoke("Hide");

button1.Invoke("ResumeLayout", true);

<强>解决方案:

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;
    }