【问题标题】:A better way to write extension method to invoke a control?编写扩展方法来调用控件的更好方法?
【发布时间】:2012-12-24 10:10:27
【问题描述】:

我有这个通用函数来调用 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# 中还是这样?

【问题讨论】:

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


【解决方案1】:

为了构建其他答案,我会将其放入单独的扩展类中。

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

【讨论】:

    【解决方案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);
    }
    

    抱歉,回答太长了,希望对您有所帮助。

    【讨论】:

    • 好答案。我在想一些我不需要指定button1 两次的事情。
    • 是的,我认为这是不可能的——因为您需要指定 Invoke() 方法的对象和 Hide() 方法的对象。您当然可以派生 Button 类并添加 InvokeHide() 之类的方法,但由于您的目标是尽量减少输入,我认为这不是一个好主意 :-)
    【解决方案3】:

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

    Post 异步执行,而 Send 阻塞直到操作完成。

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

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

    【讨论】:

      【解决方案4】:

      我会选择:

      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 的参数)。我相信这是优雅的语法。

      【讨论】:

        【解决方案5】:

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

        【讨论】:

        • @nawfal,我同意。但是在((Action)button1.Hide).Invoke();button1.Invoke((c) =&gt; c.Hide()); 之间选择我会选择最后一个:)
        • 我会这样做是因为,特别是对于带有参数的方法@cre8or 的解决方案将变为((Action&lt;bool&gt;)button1.ResumeLayout).Invoke(true); 与原始button1.Invoke(c =&gt; c.ResumeLayout(true));
        • cre8or 的解决方案不会变成((Action&lt;bool&gt;)button1.ResumeLayout).Invoke(true);,因为他没有推荐它(正如他正确指出的那样,不会避免跨线程错误)。他刚才提到了。
        • @nawfal,那么他推荐的解决方案是什么?好吧,button1.Invoke(button1.Hide); 将在稍作修复后编译。但它仍然很糟糕:button1.Invoke((Action&lt;bool&gt;)button1.ResumeLayout, true);button1.Invoke((Action&lt;int, int, int, int&gt;)button1.SetBounds, 0, 0, 10, 10);。它还破坏了类型安全。虽然,我同意我的解决方案的类型安全性更低。
        • 他提供了几个选项让我选择一个。我采用了第一种方法,这没什么大不了的。我只是知道其他更好的答案(如果有的话)。看第三个答案,更好。
        猜你喜欢
        • 2015-09-01
        • 2019-07-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-02-23
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多