为什么必须在作为普通的Delegate参数提供时转换lambda表达式
题
采用System.Windows.Forms.Control.Invoke(Delegate方法)方法
为什么会出现编译时错误:
string str = "woop";
Invoke(() => this.Text = str);
// Error: Cannot convert lambda expression to type 'System.Delegate'
// because it is not a delegate type
然而这很好用:
string str = "woop";
Invoke((Action)(() => this.Text = str));
当方法需要普通的Delegate?
时解决方案
lambda表达式可以转换为委托类型或表达式树 - 但它必须知道哪个委托类型。只知道签名是不够的。例如,假设我有:
public delegate void Action1();
public delegate void Action2();
...
Delegate x = () => Console.WriteLine("hi");
您希望x
引用的对象的具体类型是什么?是的,编译器可以生成一个带有适当签名的新委托类型,但这很少有用,最终你可以减少错误检查的机会。
如果您希望通过Control.Invoke
轻松调用Action
,最简单的方法是向控件添加扩展方法:
public static void Invoke(this Control control, Action action)
{
control.Invoke((Delegate) action);
}
其他提示
厌倦了一遍又一遍地抛弃lambda?
public sealed class Lambda<T>
{
public static Func<T, T> Cast = x => x;
}
public class Example
{
public void Run()
{
// Declare
var c = Lambda<Func<int, string>>.Cast;
// Use
var f1 = c(x => x.ToString());
var f2 = c(x => "Hello!");
var f3 = c(x => (x + x).ToString());
}
}
人们得到这个的时间只有十分之九,因为他们正试图编组到UI线程上。这是懒惰的方式:
static void UI(Action action)
{
System.Windows.Threading.Dispatcher.CurrentDispatcher.BeginInvoke(action);
}
现在已经输入了,问题就消失了(qv Skeet's anwer),我们有这种非常简洁的语法:
int foo = 5;
public void SomeMethod()
{
var bar = "a string";
UI(() =>
{
//lifting is marvellous, anything in scope where the lambda
//expression is defined is available to the asynch code
someTextBlock.Text = string.Format("{0} = {1}", foo, bar);
});
}
对于奖励积分,这是另一个提示。您不会为UI内容执行此操作,但在需要SomeMethod阻塞直到完成(例如请求/响应I / O,等待响应)的情况下,请使用 WaitHandle (qv msdn WaitAll,WaitAny,WaitOne)。
请注意,AutoResetEvent是WaitHandle的衍生产品。
public void BlockingMethod()
{
AutoResetEvent are = new AutoResetEvent(false);
ThreadPool.QueueUserWorkItem ((state) =>
{
//do asynch stuff
are.Set();
});
are.WaitOne(); //don't exit till asynch stuff finishes
}
最后的提示是因为事情可能会纠结:WaitHandles会阻止线程。这是他们应该做的。如果您尝试在停止的情况下编组到UI线程,则您的应用程序将挂起。在这种情况下(a)一些严重的重构是有序的,(b)作为临时黑客,你可以这样等待:
bool wait = true;
ThreadPool.QueueUserWorkItem ((state) =>
{
//do asynch stuff
wait = false;
});
while (wait) Thread.Sleep(100);
private void UIA(Action action) {this.Invoke(action);}
private T UIF<T>(Func<T> func) {return (T)this.Invoke(func);}
我将这两个功能放入我的表格应用程序中,我可以像这样的后台工作人员打电话
int row = 5;
string ip = UIF<string>(() => this.GetIp(row));
bool r = GoPingIt(ip);
UIA(() => this.SetPing(i, r));
也许有点懒,但我没有设置工人完成的功能, 在这样的情况下,这是非常方便的
private void Ping_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
int count = this.dg.Rows.Count;
System.Threading.Tasks.Parallel.For(0, count, i =>
{
string ip = UIF<string>(() => this.GetIp(i));
bool r = GoPingIt(ip);
UIA(() => this.SetPing(i, r));
});
UIA(() => SetAllControlsEnabled(true));
}
基本上,从gui DataGridView获取一些ip地址,ping它们,将结果图标设置为绿色或红色,并重新启用表单上的按钮。是的,它是<!> quot; parallel.for <!> quot;在背景工作者。是的,它有很多调用开销,但它对于短列表来说可以忽略不计,而且代码更紧凑。
我尝试在 @Andrey Naumov 的回答中构建它。可能这是一个小小的改进。
public sealed class Lambda<S>
{
public static Func<S, T> CreateFunc<T>(Func<S, T> func)
{
return func;
}
public static Expression<Func<S, T>> CreateExpression<T>(Expression<Func<S, T>> expression)
{
return expression;
}
public Func<S, T> Func<T>(Func<S, T> func)
{
return func;
}
public Expression<Func<S, T>> Expression<T>(Expression<Func<S, T>> expression)
{
return expression;
}
}
其中type参数S
是形式参数(输入参数,这是推断其余类型所需的最小值)。现在你可以这样称呼它:
var l = new Lambda<int>();
var d1 = l.Func(x => x.ToString());
var e1 = l.Expression(x => "Hello!");
var d2 = l.Func(x => x + x);
//or if you have only one lambda, consider a static overload
var e2 = Lambda<int>.CreateExpression(x => "Hello!");
您可以在同一个类中为Action<S>
和Expression<Action<S>>
添加额外的重载。对于内置委托和表达式类型的其他,您必须编写单独的类,如Lambda
,Lambda<S, T>
,Lambda<S, T, U>
等。
这方面的优点我看到了最初的方法:
-
少一种类型规范(只需要指定形式参数)。
-
这使您可以自由地使用它来对抗任何
Func<int, T>
,而不仅仅是T
时,string
,如示例所示。 -
立即支持表达式。在之前的方法中,您必须再次指定类型,例如:
var e = Lambda<Expression<Func<int, string>>>.Cast(x => "Hello!"); //or in case 'Cast' is an instance member on non-generic 'Lambda' class: var e = lambda.Cast<Expression<Func<int, string>>>(x => "Hello!");
表达式。
-
扩展其他委托(和表达式)类型的类同样繁琐。
var e = Lambda<Action<int>>.Cast(x => x.ToString()); //or for Expression<Action<T>> if 'Cast' is an instance member on non-generic 'Lambda' class: var e = lambda.Cast<Expression<Action<int>>>(x => x.ToString());
醇>
在我的方法中,你只需要声明一次类型(对于Func
s来说也少一些)。
实现安德烈答案的另一种方式就是不完全通用
public sealed class Lambda<T>
{
public static Func<Func<T, object>, Func<T, object>> Func = x => x;
public static Func<Expression<Func<T, object>>, Expression<Func<T, object>>> Expression = x => x;
}
所以事情减少到:
var l = Lambda<int>.Expression;
var e1 = l(x => x.ToString());
var e2 = l(x => "Hello!");
var e3 = l(x => x + x);
这更少打字,但你失去了某种类型的安全性, imo,这是不值得的。
派对迟到但你也可以像这样投票
this.BeginInvoke((Action)delegate {
// do awesome stuff
});
this.Dispatcher.Invoke((Action)(() => { textBox1.Text = "Test 123"; }));