淺談如何使用Lambda表達(dá)式做抽象代表
Lambda表達(dá)比代表定義和帶外方法定義的結(jié)合更清楚,且相關(guān)的額外工作只需要滿足語(yǔ)言定義即可。不過,它也有一些不足之處。如果某個(gè)方法的參數(shù)包含System.Delegate 這樣的抽象類型,用lambda表達(dá)式介紹特殊的問題:C#編譯器不能將lambda表達(dá)式轉(zhuǎn)換成還未明確定義的衍生代表類型。
如果不仔細(xì)思考一下,你的代碼看上去就會(huì)像是來自.NET1.0的東西。在本文中,我將告訴告訴你為什么lambda表達(dá)式不足以被直接轉(zhuǎn)換成抽象代表類型,并且教你怎樣使得編譯器轉(zhuǎn)換你所定義的指定代表。解決方案依賴于Windows Presentation Foundation(WPF)和System.Windows.Threading.Dispatcher組件,但是嚴(yán)格意義上說,該問題不是一個(gè)WPF問題。文中所描述的問題出現(xiàn)在若干.NET框架中,包括Windows Forms,Office 應(yīng)用程序接口和映射應(yīng)用程序接口。你可以按照下列方法來處理同類問題。
無論我什么時(shí)候使用.NET框架中帶有來自代表表格的參數(shù)的應(yīng)用程序接口,我都會(huì)傾向于使用lambda表達(dá)式而不是更詳細(xì)的表達(dá)式。例如,這行代碼創(chuàng)建了一個(gè)System.Windows.Threading.Timer,在計(jì)時(shí)器失效時(shí),該代碼調(diào)用了一個(gè)TickHandler方法:
tick = new System.Threading.Timer((unused) => |
如果方法的內(nèi)容足夠少,我就會(huì)用方法的內(nèi)容替代TickHandler()方法調(diào)用。該方法大多數(shù)情況下都有效,但是當(dāng)應(yīng)用程序接口將System.Delegate作為參數(shù)時(shí),這一技巧不管用。例如,我們將System.Windows.Controls.Dispatcher.Invoke()方法穿過WPF中的線程實(shí)施調(diào)用:
public object Invoke( |
現(xiàn)在考慮一下當(dāng)我們嘗試用lambda表達(dá)式來執(zhí)行這樣的調(diào)用時(shí),將會(huì)發(fā)生什么:
MyTime.Dispatcher.Invoke(() => DoSomething()); |
會(huì)出現(xiàn)隱秘錯(cuò)誤:
error CS1660: Cannot convert lambda expression to |
或許第一次看到這個(gè)錯(cuò)誤的時(shí)候,你還不知道到底是怎么一回事。當(dāng)然,這的確是一個(gè)代表類型。編譯器不像人一樣的靈活。System.Delegate類型是一個(gè)抽象類型,且該類型的推理工具不能推斷出自變量或某些用于未知代表類型的返回值的數(shù)量和種類。要解決這一問題,我們必須創(chuàng)建一個(gè)具體的代表類型并為該類型指定lambda表達(dá)式。記住,代表類型要求你將方法視為數(shù)據(jù)。
我創(chuàng)建了一個(gè)WPF計(jì)時(shí)器程序來展示其工作原理,其中闡述了C#3.0 怎樣簡(jiǎn)化與老式應(yīng)用程序接口(下圖)的運(yùn)行。
當(dāng)你做演示的時(shí)候,該示例中的應(yīng)用程序運(yùn)行了一個(gè)計(jì)時(shí)器,隨著設(shè)定時(shí)間流逝,它的顏色會(huì)從綠色轉(zhuǎn)為黃色再轉(zhuǎn)為紅色。這是一個(gè)很好的演示跨線程調(diào)用的方法,因?yàn)樵撚?jì)時(shí)器在背景線程中運(yùn)行。
按照時(shí)間的改變來更新演示要求對(duì)出自計(jì)時(shí)器的事件作出響應(yīng)。計(jì)時(shí)器在背景線程中運(yùn)行,所以你會(huì)很輕易地犯我們?cè)谇懊嫣岬竭^的錯(cuò)誤。
更新應(yīng)用程序
用戶界面處理的是簡(jiǎn)單代碼。當(dāng)計(jì)時(shí)器失效時(shí)它會(huì)生效,而且代碼會(huì)更新計(jì)時(shí)器的顯示。這一更新必須改變文本,或控制背景。如下所示:
MyTime.Background = newBrush; |
計(jì)時(shí)器在背景線程上運(yùn)行,所以你需要通過使用Dispatcher.Invoke()邊界線執(zhí)行調(diào)用。這兩行代碼是你想列入lambda表達(dá)式的代碼,不是證明方法定義的邏輯理由。但是我之前就講過lambda不會(huì)與Didpatcher.Invoke一起運(yùn)行,除非是你使用了具體的代表定義才行。這之中的一部分已經(jīng)在.NET框架3.5中定義了。我們可以使用嵌入式代表定義并對(duì)它們進(jìn)行分配,這些都是的該解決方案比起先前提到過的案例都要省事一些。這兩行代碼也要求一對(duì)參數(shù):一個(gè)用于文本的字符串和用于背景顏色的顏色刷。這意味著你需要使用的代表定義要考慮到這兩個(gè)參數(shù)并返回?zé)o效值:
Action
在聲明變量后,你可以為代碼指定需要執(zhí)行的代表變量。這里你可以使用lambda表達(dá)式,因?yàn)锳ction
updateTimer = (label, newBrush) => |
現(xiàn)在,當(dāng)計(jì)時(shí)器提出事件時(shí),你已經(jīng)擁有了一些需要執(zhí)行的指向該代碼的變量。接下來要做的就只是通過Dispatcher.Invoke()使用代表定義:
if (!MyTime.Dispatcher.CheckAccess()) |
這一過程十分簡(jiǎn)單,但是卻要求你反復(fù)進(jìn)行,因此,我們可以讓步驟變得容易一點(diǎn)。
這里其實(shí)由一個(gè)簡(jiǎn)單的模式。事件處理器可以從背景線程中調(diào)用出來。當(dāng)我們使用計(jì)時(shí)器,或者異步調(diào)用Web服務(wù)以及其他類似任務(wù)的時(shí)候,你就會(huì)看到這一行為。無論是在什么時(shí)候,我們都不清楚自己位于哪個(gè)線程之上,我們可以調(diào)用Dispatcher.CheckAccess()來決定是否可以訪問任意用戶界面控件。如果需要從線程邊界執(zhí)行調(diào)用,就必須使用Dispatcher.Invoke()。Dispatcher.Invoke()方法避免了由于使用了方法參數(shù)的參數(shù)數(shù)組而造成的若干超載問題。它使用的是一個(gè)我們想要執(zhí)行的抽象代表類型。
你想要一個(gè)能檢查是否需要整理編排的單一方法。如果需要,則方法會(huì)編排好調(diào)用,否則,會(huì)調(diào)用由代表指定的方法。你虛偽方法作為System.Windows.Controls.Control 類型的成員出現(xiàn)。這樣使得你可以將代碼作為控件的一部分來使用。C#3.0就為你提供了這樣做的方法:擴(kuò)展方法。你需要編寫一些方法的不同超載,這些使得你可以通過不同的參數(shù)來使用它們:
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);
}
}
當(dāng)然,我們也可以通過添加更多參數(shù)的方式來添加更多超載以擴(kuò)展這個(gè)類。這其實(shí)是一個(gè)簡(jiǎn)單的擴(kuò)展。
有一種方法讓W(xué)PF設(shè)計(jì)師們瘋狂:他們希望用最小化應(yīng)用程序接口的面積部分來簡(jiǎn)化Dispatcher對(duì)象的使用。通過使用抽象代表和參數(shù)列表中的參數(shù),這一對(duì)象的使用范圍被擴(kuò)大了。任何帶有參數(shù)的方法都可以被拿來使用。但是,這樣做有一個(gè)不足之處。該應(yīng)用程序接口更為抽象,它會(huì)破壞所有類型的安全性,而且這樣做會(huì)損壞編譯器使用類型推理的能力,從而降低工作效率。需要做的應(yīng)該是添加自己的安全擴(kuò)展方法的層類型,這一層類型可以在類型安全調(diào)用和更為抽象的.NET庫(kù)應(yīng)用程序接口之間提供一個(gè)層。
【編輯推薦】