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# 怎樣簡化與老式應用程序接口(下圖)的運行
當你做演示的時候該示例中的應用程序運行了一個計時器隨著設定時間流逝它的顏色會從綠色轉為黃色再轉為紅色這是一個很好的演示跨線程調用的方法因為該計時器在背景線程中運行
按照時間的改變來更新演示要求對出自計時器的事件作出響應計時器在背景線程中運行所以你會很輕易地犯我們在前面提到過的錯誤
更新應用程序
用戶界面處理的是簡單代碼當計時器失效時它會生效而且代碼會更新計時器的顯示這一更新必須改變文本或控制背景如下所示
MyTimeBackground = newBrush;
MyTimeContent = label
計時器在背景線程上運行所以你需要通過使用DispatcherInvoke()邊界線執行調用這兩行代碼是你想列入lambda表達式的代碼不是證明方法定義的邏輯理由但是我之前就講過lambda不會與DidpatcherInvoke一起運行除非是你使用了具體的代表定義才行這之中的一部分已經在NET框架中定義了
我們可以使用嵌入式代表定義並對它們進行分配這些都是的該解決方案比起先前提到過的案例都要省事一些這兩行代碼也要求一對參數一個用於文本的字符串和用於背景顏色的顏色刷這意味著你需要使用的代表定義要考慮到這兩個參數並返回無效值
Action updateTimer;
在聲明變量後你可以為代碼指定需要執行的代表變量這裡你可以使用lambda表達式因為Action是一個具體的代表定義
updateTimer = (label newBrush) =>
{
MyTimeBackground = newBrush;
MyTimeContent = label;
};
現在當計時器提出事件時你已經擁有了一些需要執行的指向該代碼的變量接下來要做的就只是通過DispatcherInvoke()使用代表定義
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(this
Controlwidget Action
whatToDo
T parm T parm)
{
if (!widgetDispatcher
CheckAccess())
widgetDispatcher
Invoke(whatToDo
parm parm);
else
whatToDo(parm parm);
}
}
當然我們也可以通過添加更多參數的方式來添加更多超載以擴展這個類這其實是一個簡單的擴展
有一種方法讓WPF設計師們瘋狂他們希望用最小化應用程序接口的面積部分來簡化Dispatcher對象的使用通過使用抽象代表和參數列表中的參數這一對象的使用范圍被擴大了
任何帶有參數的方法都可以被拿來使用但是這樣做有一個不足之處該應用程序接口更為抽象
它會破壞所有類型的安全性而且這樣做會損壞編譯器使用類型推理的能力從而降低工作效率需要做的應該是添加自己的安全擴展方法的層類型這一層類型可以在類型安全調用和更為抽象的NET庫應用程序接口之間提供一個層
From:http://tw.wingwit.com/Article/program/net/201311/12838.html