Lambda表达比代表定义和带外方法定义的结合更清楚,且相关的额外工作只需要满足语言定义即可。不过,它也有一些不足之处。如果某个方法的参数包含System.Delegate 这样的抽象类型,用lambda表达式介绍特殊的问题:C#编译器不能将lambda表达式转换成还未明确定义的衍生代表类型。
如果不仔细思考一下,你的代码看上去就会像是来自.NET1.0的东西。在本文中,我将告诉告诉你为什么lambda表达式不足以被直接转换成抽象代表类型,并且教你怎样使得编译器转换你所定义的指定代表。解决方案依赖于Windows Presentation Foundation(WPF)和System.Windows.Threading.Dispatcher组件,但是严格意义上说,该问题不是一个WPF问题。文中所描述的问题出现在若干.NET框架中,包括Windows Forms,Office 应用程序接口和映射应用程序接口。你可以按照下列方法来处理同类问题。
无论我什么时候使用.NET框架中带有来自代表表格的参数的应用程序接口,我都会倾向于使用lambda表达式而不是更详细的表达式。例如,这行代码创建了一个System.Windows.Threading.Timer,在计时器失效时,该代码调用了一个TickHandler方法:
tick = new System.Threading.Timer((unused) => |
如果方法的内容足够少,我就会用方法的内容替代TickHandler()方法调用。该方法大多数情况下都有效,但是当应用程序接口将System.Delegate作为参数时,这一技巧不管用。例如,我们将System.Windows.Controls.Dispatcher.Invoke()方法穿过WPF中的线程实施调用:
public object Invoke( |
现在考虑一下当我们尝试用lambda表达式来执行这样的调用时,将会发生什么:
MyTime.Dispatcher.Invoke(() => DoSomething()); |
会出现隐秘错误:
error CS1660: Cannot convert lambda expression to |
或许第一次看到这个错误的时候,你还不知道到底是怎么一回事。当然,这的确是一个代表类型。编译器不像人一样的灵活。System.Delegate类型是一个抽象类型,且该类型的推理工具不能推断出自变量或某些用于未知代表类型的返回值的数量和种类。要解决这一问题,我们必须创建一个具体的代表类型并为该类型指定lambda表达式。记住,代表类型要求你将方法视为数据。
我创建了一个WPF计时器程序来展示其工作原理,其中阐述了C#3.0 怎样简化与老式应用程序接口(下图)的运行。
当你做演示的时候,该示例中的应用程序运行了一个计时器,随着设定时间流逝,它的颜色会从绿色转为黄色再转为红色。这是一个很好的演示跨线程调用的方法,因为该计时器在背景线程中运行。
按照时间的改变来更新演示要求对出自计时器的事件作出响应。计时器在背景线程中运行,所以你会很轻易地犯我们在前面提到过的错误。
更新应用程序
用户界面处理的是简单代码。当计时器失效时它会生效,而且代码会更新计时器的显示。这一更新必须改变文本,或控制背景。如下所示:
MyTime.Background = newBrush; |
计时器在背景线程上运行,所以你需要通过使用Dispatcher.Invoke()边界线执行调用。这两行代码是你想列入lambda表达式的代码,不是证明方法定义的逻辑理由。但是我之前就讲过lambda不会与Didpatcher.Invoke一起运行,除非是你使用了具体的代表定义才行。这之中的一部分已经在.NET框架3.5中定义了。我们可以使用嵌入式代表定义并对它们进行分配,这些都是的该解决方案比起先前提到过的案例都要省事一些。这两行代码也要求一对参数:一个用于文本的字符串和用于背景颜色的颜色刷。这意味着你需要使用的代表定义要考虑到这两个参数并返回无效值:
Action
在声明变量后,你可以为代码指定需要执行的代表变量。这里你可以使用lambda表达式,因为Action
updateTimer = (label, newBrush) => |
现在,当计时器提出事件时,你已经拥有了一些需要执行的指向该代码的变量。接下来要做的就只是通过Dispatcher.Invoke()使用代表定义:
if (!MyTime.Dispatcher.CheckAccess()) |
这一过程十分简单,但是却要求你反复进行,因此,我们可以让步骤变得容易一点。
这里其实由一个简单的模式。事件处理器可以从背景线程中调用出来。当我们使用计时器,或者异步调用Web服务以及其他类似任务的时候,你就会看到这一行为。无论是在什么时候,我们都不清楚自己位于哪个线程之上,我们可以调用Dispatcher.CheckAccess()来决定是否可以访问任意用户界面控件。如果需要从线程边界执行调用,就必须使用Dispatcher.Invoke()。Dispatcher.Invoke()方法避免了由于使用了方法参数的参数数组而造成的若干超载问题。它使用的是一个我们想要执行的抽象代表类型。
你想要一个能检查是否需要整理编排的单一方法。如果需要,则方法会编排好调用,否则,会调用由代表指定的方法。你虚伪方法作为System.Windows.Controls.Control 类型的成员出现。这样使得你可以将代码作为控件的一部分来使用。C#3.0就为你提供了这样做的方法:扩展方法。你需要编写一些方法的不同超载,这些使得你可以通过不同的参数来使用它们:
public static class WPFExtensions:{
public static voidInvokeIfNeeded(
this Control widget,
Action whatToDo)
{
if (!widget.Dispatcher.
CheckAccess())
widget.Dispatcher.Invoke(whatToDo);
else
whatToDo();
}
public static void
InvokeIfNeeded
( this Controlwidget, Action
whatToDo, T parm)
{
if (!widget.Dispatcher.CheckAccess())
widget.Dispatcher.Invoke(whatToDo, parm);
else
whatToDo(parm);
}
public static void
InvokeIfNeeded
(this Controlwidget, Action
whatToDo,
T1 parm1, T2 parm2)
{
if (!widget.Dispatcher.
CheckAccess())
widget.Dispatcher.
Invoke(whatToDo,
parm1, parm2);
else
whatToDo(parm1, parm2);
}
}
当然,我们也可以通过添加更多参数的方式来添加更多超载以扩展这个类。这其实是一个简单的扩展。
有一种方法让WPF设计师们疯狂:他们希望用最小化应用程序接口的面积部分来简化Dispatcher对象的使用。通过使用抽象代表和参数列表中的参数,这一对象的使用范围被扩大了。任何带有参数的方法都可以被拿来使用。但是,这样做有一个不足之处。该应用程序接口更为抽象,它会破坏所有类型的安全性,而且这样做会损坏编译器使用类型推理的能力,从而降低工作效率。需要做的应该是添加自己的安全扩展方法的层类型,这一层类型可以在类型安全调用和更为抽象的.NET库应用程序接口之间提供一个层。
【编辑推荐】