为什么在作为普通委托参数提供时,必须将lambda表达式进行强制转换?
为什么在作为普通委托参数时必须对lambda表达式进行转换?
在上面的代码中,Lambda
这个Lambda
在Example类中的Run方法中,使用了Lambda
Lambda
为什么在作为普通委托参数提供时,lambda表达式必须进行强制转换?
lambda表达式可以转换为委托类型或表达式树,但它必须知道具体的委托类型。仅仅知道签名是不够的。例如,假设我有以下代码:
public delegate void Action1(); public delegate void Action2(); ... Delegate x = () => Console.WriteLine("hi");
你会期望`x`引用的对象的具体类型是什么?是的,编译器可以生成一个具有适当签名的新委托类型,但这很少有用,并且会减少错误检查的机会。
如果你想要调用`Control.Invoke`并传入一个`Action`,最简单的方法是为`Control`添加一个扩展方法:
public static void Invoke(this Control control, Action action) { control.Invoke((Delegate) action); }
谢谢,我更新了问题,因为我认为“untyped”不是正确的术语。
这是一个非常优雅和成熟的解决方案。我可能会将其命名为“InvokeAction”,以便该名称暗示我们实际调用的内容(而不是一个通用委托),但对我来说肯定可行。
我不同意它“很少有用...”。在使用lambda调用Begin/Invoke的情况下,你确实不关心委托类型是否是自动生成的,我们只是希望进行调用。在什么情况下,接受委托(基本类型)的方法会关心具体的类型是什么?此外,扩展方法的目的是什么?它并没有使任何事情变得更容易。
扩展方法确实使事情变得更容易。你可以直接调用`control.Invoke(() => DoStuff)`,因为从lambda表达式到`Action`有一个转换。这不正是我们想要的吗?至于“自动生成”的委托是否有用 - 我认为问题在于`Delegate`实际上并不合适。如果给`Control.Invoke`传递了一个具有10个参数的委托,它应该传递什么?在.NET 1中,它是最好的选择,但是使用泛型后,我认为有更好的方法。
啊!我添加了扩展方法并尝试了`Invoke(() => DoStuff)`,仍然出现错误。问题在于我使用了隐式的`this`。要在控件成员内部使其工作,你必须明确使用:`this.Invoke(() => DoStuff)`。
对其他人来说,我认为阅读此问题和答案的C#: Automating the InvokeRequired code pattern非常有帮助。
这是一个很好的答案!类似地,你不能直接将匿名委托转换为`LambdaExpression`,而是必须使用指定类型的确切表达式进行转换。
lambda表达式必须在作为普通委托参数传递时进行强制转换的原因是,大部分情况下,人们之所以遇到这个问题是因为他们想要将代码调度到UI线程。通过下面的代码可以解决这个问题:
static void UI(Action action) { System.Windows.Threading.Dispatcher.CurrentDispatcher.BeginInvoke(action); }
通过这种方式,问题迎刃而解,并且我们还能够使用非常简洁的语法:
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); }); }
如果需要在SomeMethod方法完成之前阻塞(例如请求/响应I/O,等待响应),可以使用WaitHandle。注意,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 }
在处理代码会变得混乱的情况下,WaitHandle会阻塞线程。如果您尝试在UI线程上进行调度而同时阻塞该线程,您的应用程序将挂起。在这种情况下,需要进行一些严重的重构,并且作为一个临时解决方法,可以使用以下方式进行等待:
bool wait = true; ThreadPool.QueueUserWorkItem((state) => { //do asynch stuff wait = false; }); while (wait) Thread.Sleep(100);
令人惊讶的是,有些人会因为个人不喜欢而对答案进行投票否定。如果答案是错误的,并且你知道这一点,请指出其中的错误之处。如果你无法做到这一点,那么你就没有投票的依据。如果答案是极其错误的,那么可以说一些类似于“胡说八道。参见[正确的回答]”或者“不推荐的解决方案,请参见[更好的解决方案]”之类的话。
System.Windows.Threading.Dispatcher.CurrentDispatcher
将返回当前线程的调度器,即如果您从非UI线程调用此方法,代码将不会在UI线程上运行。
好的建议是,应用程序应该捕获对UI线程调度器的引用,并将其放在全局可访问的位置。令人惊讶的是,花了这么长时间才有人注意到这一点!