问题
I have this general function to invoke a WinForm control:
public static void Invoke(this Control c, Action action)
{
if (c.InvokeRequired)
c.TopLevelControl.Invoke(action);
else
action();
}
I'm thinking of making it better by bringing harsher constraints to prevent things that are meaningless, may be like:
button1.Invoke(() => list.Add(1));
Also there can be redundant typing like:
button1.Invoke(() => button1.Hide());
since we are already specifying the this
is button1
.
So I made it:
public static void Invoke<T>(this T c, Action<T> action) where T : Control
{
if (c.InvokeRequired)
c.TopLevelControl.Invoke(action);
else
action(c);
}
Now I'll have to call,
button1.Invoke((c) => c.Hide());
or
button1.Invoke((c) => button1.Hide());
Now I kind of feel that even then there is some more than required typing. If I'm specifying this
is button1
, then in the lambda expression I do not want to specify a dummy variable c
again to tell where to operate on. Is there anyway I can make this shorter again? Perhaps like
button1.Invoke(Hide);
or
button1.Hide.Invoke();
or so in C#?
回答1:
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.
回答2:
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());
回答3:
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);
回答4:
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.
回答5:
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;
}
来源:https://stackoverflow.com/questions/14019896/a-better-way-to-write-extension-method-to-invoke-a-control