C#中的匿名委托

34

我不可能是唯一一个因为某些需要委托的调用而不得不频繁定义和命名委托而感到疲倦的人。例如,我想从可能来自其他线程的地方调用一个窗体的.Refresh()方法,所以我写了这段代码:

private void RefreshForm()
{
    if (InvokeRequired)
        Invoke(new InvokeDelegate(Refresh));
    else
        Refresh();
}

我甚至不确定是否必须这样做,只是读了足够多的内容,担心它在后面某个阶段无法正常工作。
InvokeDelegate实际上是在另一个文件中声明的,但我真的需要专门为此分配整个委托吗?难道没有通用的委托吗?
我的意思是,例如有一个Pen类,但也有Pens.pen-of-choice,这样你就不必重新制作整个事物。虽然不完全相同,但我希望你明白我的意思。


1
在C#中并不存在“匿名委托”这种东西。有委托(就像你正在使用的那样)和匿名方法(只能通过委托访问)。 - Lucas
3
我猜测由于 C# 规范使用了“匿名方法”的术语,这应该是正确的方式。然而,很多人使用这个术语来表示用匿名方法作为参数实例化的委托。所以,它们的意思相同,但某个人在何时使用其中一种术语取决于是否发生了委托分配或匿名方法直接传递到另一个方法的情境。我欢迎任何反对意见。 - Richard Anthony Freeman-Hein
6个回答

52

是的,在.NET 3.5中,您可以使用FuncAction委托。 Func委托返回一个值,而Action委托返回void。以下是类型名称的示例:

System.Func<TReturn> // (no arg, with return value)
System.Func<T, TReturn> // (1 arg, with return value)
System.Func<T1, T2, TReturn> // (2 arg, with return value)
System.Func<T1, T2, T3, TReturn> // (3 arg, with return value)
System.Func<T1, T2, T3, T4, TReturn> // (4 arg, with return value)

System.Action // (no arg, no return value)
System.Action<T> // (1 arg, no return value)
System.Action<T1, T2> // (2 arg, no return value)
System.Action<T1, T2, T3> // (3 arg, no return value)
System.Action<T1, T2, T3, T4> // (4 arg, no return value)

我不知道他们为什么只限制了每个参数最多4个,但对于我来说这总是足够的。


2
4不是一个具体的规则,但在此之后,您至少应该考虑以某种方式组合参数。 - Matthew Vines
1
当我将一些旧的.NET代码移植到3.5时,如果我看到委托,我会将它们替换为Func或Action。 - RichardOD
10
下一个版本的框架将包括四个以上参数的 Func 和 Action。 - Eric Lippert

26

你可以使用Action委托,像这样:

private void RefreshForm()
{
    if (InvokeRequired) Invoke(new Action(Refresh));
    else Refresh();
}

或者,使用 lambda 语法:

private void RefreshForm()
{
    if (InvokeRequired) Invoke((Action)(() => Refresh()));
    else Refresh();
}

最后还有匿名委托语法:

private void RefreshForm()
{
    if (InvokeRequired) Invoke((Action)(delegate { Refresh(); }));
    else Refresh();
}

2
奇怪的是,第二个和第三个语法不能正常工作,说它无法将其转换为 System.Delegate,因为它不是委托类型。但是,Action 绝对是我想要的。 - Nefzen
1
匿名委托无法以此方式传递给Invoke,因为它们不是Delegate类型。你在这里写的甚至都无法编译。 - Brian ONeil
1
呵呵,这就是我在发布前没有检查的后果。我会尽快修复。 - Erik Forbes
2
将 lambda 和匿名委托转换为 Action 就可以解决问题。对于造成的困惑,我表示抱歉。 - Erik Forbes
5
这篇文章非常有帮助。 - Stan R.
显示剩余2条评论

8
在这种特定情况下,您可以(而且应该)只使用MethodInvoker来完成此操作......这就是它存在的原因。
if (InvokeRequired)
    Invoke(new MethodInvoker(Refresh));
else
    Refresh();

如果您正在做其他事情,可以像其他人已经回答的那样使用Func<T,...>或Action<T,...>,如果它们符合您的用例。


1
这是.NET 2.0的最佳解决方案。 - Jan Willem B

5

简短版:

Invoke((MethodInvoker)delegate { Refresh(); });

那么你还可以省略InvokeRequired的检查,直接调用它即可。如果需要传递参数,也可以正常工作,因此不需要其他特定于参数的委托(使用无参数的Action委托同样有效):

private void SetControlText(Control ctl, string text)
{
    Invoke((MethodInvoker)delegate { ctl.Text = text; });
}

2
@Brian:你的意思是这样吗:Invoke(new MethodInvoker(delegate { ctl.Text = text; }));?就我所看到的,它产生的IL代码与我上面的代码完全相同,所以这取决于个人口味,我想。 - Fredrik Mörk
@Fredrik:Brian 的意思是使用 new MethodInvoker(Refresh) 而不是 (MethodInvoker)delegate { Refresh(); }。 - Nefzen
如果最终生成的IL代码相同,那么我想这更多是关于个人偏好的问题,但如果委托存在,我不明白为什么你会更喜欢使用匿名语法。似乎直接声明并使用它更加简单明了。 - Brian ONeil
1
使用Invoke((MethodInvoker)Refresh)或Invoke(new MethodInvoker(Refresh))代替Invoke((MethodInvoker)delegate { Refresh(); }),后者通过创建一个仅调用Refresh()的匿名方法来创建一个额外的间接级别,而不是创建对Refresh()本身的委托。 - Lucas
@Purusartha:听起来很奇怪。你能提供重现这个问题的代码吗?我很想看看,以便我可以纠正任何错误。 - Fredrik Mörk
显示剩余2条评论

2
我真的需要一个专门用于此的完整委托吗?难道没有任何通用委托吗?
定义自己的委托确实可以使调试更容易,因为Intellisense可以告诉您参数的名称。例如,您可以编写如下委托:
public delegate int UpdateDelegate(int userID, string city, string, state, string zip);

当您使用它的代码时,.NET会告知您参数名称、委托名称等信息,因此如果您不确定如何使用某些内容,委托定义中就已经有很多上下文信息了。
但是,如果您不介意牺牲Intellisense,那么在System命名空间中已经定义了一类可用作即席委托的委托类。
Func<T>
Func<T, U>
Func<T, U, V>
Func<T, U, V, W>
Action, Action<T>
Action<T, U>
Action<T, U, V>
Action<T, U, V, W>

在.NET 2.0中,只有ActionAction存在,但很容易声明一个帮助类,并为这些杂项临时函数所需的其余委托添加。请保留HTML标签。


1

是的,有通用委托。 Action<T1, T2...> 是一个通用委托,它接受一些参数并返回没有值,而 Func<T1, T2...R> 是一个通用委托,它接受一些参数并返回一个值。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接