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