本文章是由機器翻譯。

實用模式

日常的.NET 開發函式程式設計

Jeremy Miller

什麼是.NET 生態系統中最重要的進階過去三或四個年 您可能想要命名新的技術或架構,如 Windows Communication Foundation (WCF) 或 Windows Presentation Foundation (WPF)。 為我個人,但是,我會說強大新增至 C# 和 Visual Basic 語言在最後兩個版本的.NET Framework 有更顯著影響我的日常的開發活動。 本文中我想要特別檢查新的支援的功能的程式設計技術的.NET 3.5 如何協助您執行下列動作:

  1. 使您的程式碼更宣告式。
  2. 減少程式碼中的錯誤。
  3. 撰寫的許多一般工作的程式碼較少的行。

「 語言整合式查詢 (LINQ) 功能會在所有其許多多采是在.NET 中,函式程式設計的明顯且有效使用,但這只是在提示的最 iceberg。

若要保留與 「 日常開發 」 的佈景主題我已根據 C# 3.0 與一些 JavaScript sprinkled 中大部分的我的程式碼範例。 請注意某些其他,較新的 CLR,IronPython、 IronRuby 和 F # 程式設計語言會需要更強大的支援或很多有用的語法,功能的程式設計技術,這份文件中所示。 不幸的是,Visual Basic 目前版本不支援多行 Lambda 函數,因此許多技術顯示此處不是為使用 Visual Basic。 但是,我會催促考慮這些技術,以準備下一個的語言版本的 Visual Basic 開發人員在 Visual Studio 2010 傳送。

最高級的函式

函式程式設計的一些項目可能在 C# 或 Visual Basic 因為我們現在有最高級的函式方法,之間可以傳遞周圍,保留為變數或甚至從另一個方法傳回。 從.NET 2.0] 和 [.NET 3.5 中較新的 Lambda 運算式的匿名委派是如何在 C# 和 Visual Basic 實作但 「 Lambda 運算式 」 的最高級函式表示電腦科學領域的更特定的項目。 另一種常見的說法,最高級的函式為 [封鎖]。這份文件的其餘部分,我將使用詞彙"阻隔"來代表最高級的函式而不是 「 關閉 」(我接下來討論最高級的函式的特定類型) 或 「 Lambda,"若要避免意外 inaccuracies (和發火真正功能性程式設計專家的)。 在關閉包含終止函式以外,從定義的變數。 如果您使用 JavaScript 開發越來越普遍 jQuery 媒體櫃,您可能使用 closures 相當頻繁。 以下是使用來自目前專案的一個終止的範例:

// Expand/contract body functionality          
var expandLink = $(".expandLink", itemElement);
var hideLink = $(".hideLink", itemElement);
var body = $(".expandBody", itemElement);
body.hide();

// The click handler for expandLink will use the
// body, expandLink, and hideLink variables even
// though the declaring function will have long
// since gone out of scope.
expandLink.click(function() {
    body.toggle();
    expandLink.toggle();
    hideLink.toggle();
});

此代碼用來設定顯示或隱藏在我們的 Web 網頁上的內容即可的 < 一 > 是很典型 accordion 效果項目。 我們傳遞終止函式,使用建立關閉之外的變數中定義 expandLink 按一下 [處理的常式。 函式,其中包含變數和 Click 處理常式將會結束時間之前可以按選 expandLink 使用者,但是 Click 處理常式仍然可以能夠使用在主體和 hideLink 變數。

為資料 lambdas

在某些的情況下您可以使用 Lambda 語法來表示可以被用來作為資料而執行的程式碼中的運算式。 我沒有特別的了解好幾次我閱讀該陳述式第一個,所以看一下一個 Lambda 視為從使用 Fluent NHibernate 程式庫的明確物件/關聯式對應的資料的範例:

 

public class AddressMap : DomainMap<Address>
    {
        public AddressMap()
        {
            Map(a => a.Address1);
            Map(a => a.Address2);
            Map(a => a.AddressType);
            Map(a => a.City);
            Map(a => a.TimeZone);
            Map(a => a.StateOrProvince);
            Map(a => a.Country);
            Map(a => a.PostalCode);
        }
    }

fluent NHibernate 永遠不會評估運算式為 = >a.Address1。 而是,它剖析以尋找名稱 Address1 在基礎的 NHibernate 對應中使用運算式。 這項技術都有透過.NET 空間中的許多新開放原始碼專案的廣泛散佈。 使用 Lambda 運算式只來取得 PropertyInfo 物件和屬性名稱通常稱為靜態的反映。

傳遞區塊

研究功能性程式設計,最佳理由之一是以了解如何最高級的函式可讓您減少在程式碼中的重複提供的撰寫比類別更細微的機制。 您通常會有的程式碼是在其基本的形式以外的地方順序的中間的一個步驟中基本上相同的序列。 物件導向的程式設計您可以使用範本的方法圖樣的繼承,嘗試排除重複。 越來越多找傳遞區塊代表變數步驟在中間實作基本的順序要清除方法來避免此重複情形的另一個方法。

要讓 API,更容易使用且較不容易發生錯誤,最佳的方法是減少重複的程式碼。 例如,考慮設計用來存取遠端服務或資源,如 ADO.NET IDbConnection 物件或通訊端接聽程式的需要狀態或持續連線的 API 的常見的情況。 您必須通常 「 開啟 」使用資源之前,先連線。 這些可設定狀態的連線通常是昂貴或不足的資源,所以通常重要 「 關閉 」一旦您為了釋放資源的其他處理序或執行緒連接。

下列程式碼顯示一個代表的介面,可設定狀況連線某些類型的閘道的:

 

public interface IConnectedResource
    {
        void Open();
        void Close();

        // Some methods that manipulate the connected resource
        BigDataOfSomeKind FetchData(RequestObject request);
        void Update(UpdateObject update);
    }

另一個類別使用這個 IConnectedResource,介面每一次開啟的方法必須使用其他方法之前呼叫而且 Close 方法應該永遠在呼叫之後,圖 1 所示。

在較早發行項我會討論本質與典禮的概念我們設計中。 (請參閱 msdn.microsoft.com/magazine/dd419655.aspx)。「 要素 」ConnectedSystemConsumer 的類別的責任是只更新某些資訊使用連線的資源。 不幸的是,"典禮 」 被有關的大部分 ConnectedSystemConsumer 在程式碼連接和中斷 IConnectedResource 介面和錯誤處理。

圖 1使用 IConnectedResource

 

public class ConnectedSystemConsumer
{
private readonly IConnectedResource _resource;
public ConnectedSystemConsumer(IConnectedResource resource)
{
_resource = resource;
}
public void ManipulateConnectedResource()
{
try
{
// First, you have to open a connection
_resource.Open();
// Now, manipulate the connected system
_resource.Update(buildUpdateMessage());
}
finally
{
_resource.Close();
}
}
}

更糟的是還是,「 嘗試/開啟/執行內容/最後/關閉"將重複出現 IConnectedResource 介面的每個使用對典禮程式碼。 如同我所討論之前,先,最好的方法來改善您的設計是它 creeps 至您的程式碼的地方加上戳記出重複。 讓我們試著以不同的方法來使用區塊或終止 IConnectedResource API。 第一次,我會將介面的隔離主要 (請參閱 objectmentor.com/resources/articles/isp.pdf (如需詳細資訊) 來擷取的介面嚴格的開啟或關閉叫用方法沒有連線的資源:

 

public interface IResourceInvocation
    {
        BigDataOfSomeKind FetchData(RequestObject request);
        void Update(UpdateObject update);
    }

下一步],我會建立第二個介面所使用嚴格來存取連線的資源表示 IResourceInvocation 介面:

 

public interface IResource
    {
        void Invoke(Action<IResourceInvocation> action);
    }

現在,我們重寫 ConnectedSystemConsumer 類別使用較新的、 功能樣式 API:

 

public class ConnectedSystemConsumer
    {
        private readonly IResource _resource;
 
        public ConnectedSystemConsumer(IResource resource)
        {
            _resource = resource;
        }

        public void ManipulateConnectedResource()
        {
            _resource.Invoke(x =>
            {
                x.Update(buildUpdateMessage());
            });
        }
    }

這個新的版本的 ConnectedSystemConsumer 已經不再處理有關如何設定或清除下連線的資源。 作用中 ConnectedSystemConsumer 只會告訴 「 向上移至您看到並提供它這些指示,第一個 IResourceInvocation"IResource 介面透過傳入在區塊或終止 IResource.Invoke 的方法。 所有重複 「 嘗試/開啟/執行內容/最後/關閉"我的抱怨之前的典禮程式碼目前正在 IResource 的具體實作的圖 2 所示。

圖 2 具體 [實作的 IResource

 

frepublic
class Resource : IResource
{
public void Invoke(Action<IResourceInvocation> action)
{
IResourceInvocation invocation = null;
try
{
invocation = open();
// Perform the requested action
action(invocation);
}
finally
{
close(invocation);
}
}
private void close(IResourceInvocation invocation)
{
// close and teardown the invocation object
}
private IResourceInvocation open()
{
// acquire or open the connection
}
}

我會認為我們已經將開啟和關閉連線到外部資源至資源類別負責改善我們的設計和 API 可用性。 我們也已經改善我們的程式碼的結構封裝應用程式的核心工作流程的基礎結構問題的詳細資料。 第二個版本的 ConnectedSystemConsumer 知道目前小於相關的外部連接的資源在運作了第一個版本。 第二個設計可讓您更輕鬆地變更您的系統互動方式外部連接的資源變更而可能 destabilizing 您系統的核心工作流程程式碼。

第二個的設計也會讓您的系統小於出錯藉由消除重複 「 嘗試/開啟/最後/關閉 「循環。 每次重複該程式碼對開發人員,他風險進行,因一個程式碼可以技術上正確運作,但耗盡資源並且傷害的應用程式延展性特性。

延遲的執行

若要瞭解關於功能性程式設計最重要的概念是延遲的執行。 幸運的是,此概念也是相當簡單。 所有可能的原因是,您已經定義的區塊函式內嵌一定不會立即執行。 讓我們來看延遲執行的實際使用情形。

在相當大的 WPF 應用程式中,我可以使用稱為 IStartable 資料標記介面來代表需要被,也,啟動做為應用程式的啟動載入程序的一部分的服務。

 

public interface IStartable
    {
        void Start();
    }

此特定的應用程式的所有服務登錄,且由 (在此情況下,StructureMap) 的反轉的控制項容器應用程式擷取。 在應用程式啟動時,我有下列位元的動態探索要啟動的應用程式中的所有服務,並再啟動這些程式碼:

 

// Find all the possible services in the main application
// IoC Container that implements an "IStartable" interface
List<IStartable> startables = container.Model.PluginTypes
    .Where(p => p.IsStartable())
    .Select(x => x.ToStartable()).ToList();
         

// Tell each "IStartable" to Start()
startables.Each(x => x.Start());

有三個 Lambda 運算式,此程式碼中。 讓我們假設您附加此程式碼的.NET 基底類別程式庫完整的來源的程式碼複本,並再嘗試使用偵錯工具逐步執行。 在逐步執行 [時選取,或每呼叫,您會發現 Lambda 運算式不下一個要執行的程式碼的行並且隨著這些方法會逐一 container.Model.PluginTypes 成員的內部的結構,Lambda 運算式是所有執行多次。 思考延遲執行另一個方法,當您叫用每個方法時,您正在只告訴每一個方法如何隨時就是跨 IStartable 物件。

memoization

memoization 是一種最佳化技術,用來避免執行昂貴的函式呼叫的重複使用相同的輸入與前一個執行的結果。 我先隨附到連絡對函式程式設計使用 F # 執行詞彙 memoization,但在研究本文的過程中我發現我的小組經常在我們的 C# 開發使用 memoization。 讓我們假設您經常需要擷取某種計算的財務資料指定地區與服務就像這樣:

 

public interface IFinancialDataService
    {
        FinancialData FetchData(string region);
    }

IFinancialDataService 是非常緩慢執行與財務資料是相當是以靜態的因此套用 memoization 會為應用程式回應速度很有幫助。 您可以建立 IFinancialDataService 示的圖 3 為實作 memoization 內部的 IFinancialDataService 類別的一個包裝函式的實作。

圖 3 實作的內部 IFinancialDataService 類別

 

public class MemoizedFinancialDataService : IFinancialDataService
{
private readonly Cache<string, FinancialData> _cache;
// Take in an "inner" IFinancialDataService object that actually
// fetches the financial data
public MemoizedFinancialDataService(IFinancialDataService
innerService)
{
_cache = new Cache<string, FinancialData>(region =>
innerService.FetchData(region));
}
public FinancialData FetchData(string region)
{
return _cache[region];
}
}

「 快取 < TKey、 TValue >類別本身是只包裝一個字典 < TKey、 TValue >物件。 圖 4 顯示快取類別的一部分。

圖 4 上的 [快取類別]

public class Cache<TKey, TValue> : IEnumerable<TValue> where TValue :
class
{
private readonly object _locker = new object();
private readonly IDictionary<TKey, TValue> _values;
private Func<TKey, TValue> _onMissing = delegate(TKey key)
{
string message = string.Format(
"Key '{0}' could not be found", key);
throw new KeyNotFoundException(message);
};
public Cache(Func<TKey, TValue> onMissing)
: this(new Dictionary<TKey, TValue>(), onMissing)
{
}
public Cache(IDictionary<TKey, TValue>
dictionary, Func<TKey, TValue>
onMissing)
: this(dictionary)
{
_onMissing = onMissing;
}
public TValue this[TKey key]
{
get
{
// Check first if the value for the requested key
// already exists
if (!_values.ContainsKey(key))
{
lock (_locker)
{
if (!_values.ContainsKey(key))
{
// If the value does not exist, use
// the Func<TKey, TValue> block
// specified in the constructor to
// fetch the value and put it into
// the underlying dictionary
TValue value = _onMissing(key);
_values.Add(key, value);
}
}
}
return _values[key];
}
}
}

如果您有興趣的快取類別在內部,您可以在數個的開放原始碼軟體專案包括 StructureMap,StoryTeller,FubuMVC 找到它的一個版本,我相信,Fluent NHibernate。

對應/縮小] 模式

其實許多常見的開發工作的簡單與功能的程式設計技巧。 在就特別清單,在程式碼中的設定作業支援 「 對應/降低 」 模式的語言中,以機械方式最簡單。 (LINQ,在 「 地圖 」 是 「 選取 」 和 「 縮小 」 是 「 彙總 」)。請考慮您會在如何計算總和的整數陣列。 在.NET 1.1 中,您必須如下重複陣列:

int [] 數字 = 新的 int [] {1,2,3,4,5};
int 加總 = 0 ;
為 (int i = 0 ;i <numbers.length ;+ i +)
{
加總 + = 數字 [i] ;
}

Console.WriteLine(sum) ;

音訊語言增強功能,來支援.NET 3.5 LINQ 提供減少對應/功能功能的程式語言中常見。 今天,上述程式碼只可以寫成:

int [] 數字 = 新的 int [] {1,2,3,4,5};
int 加總 = numbers.aggregate ((x, y) = >x + y) ;

也更簡單:

int 加總 = numbers.Sum() ;
Console.WriteLine(sum) ;

Continuations

大約放入程式設計中接續是的抽象概念的一些排序表示 「 內容的下一個步驟中 」或者 「 其餘的計算。"有時很寶貴在其他的時間為在精靈的應用程式的使用者可以明確允許下一步或取消整個程序完成計算程序的一部份。

讓我們來跳到程式碼範例的權限。 假設您正在開發 WinForms 或 WPF 中的桌面應用程式。 您經常需要初始化某些類型的長時間執行處理程序或螢幕動作的存取速度緩慢的外部服務。 可用性,為了的當然不想來鎖定使用者介面,讓它回應發生服務呼叫時, 讓您在背景執行緒中執行它。 當服務呼叫最後會傳回時,您可能要更新使用者介面與回來自服務,資料,但是任何有經驗的 WinForms 或 WPF 開發人員知道,您可以更新使用者介面項目只能在主要的使用者介面執行緒上。

您當然可以使用 BackgroundWorker 類別會在的 System.ComponentModel 命名空間中,但我偏好根據將 Lambda 運算式傳遞到由這個介面表示 CommandExecutor 物件以不同方法:

public interface ICommandExecutor
    {
        // Execute an operation on a background thread that
        // does not update the user interface
        void Execute(Action action);

        // Execute an operation on a background thread and
        // update the user interface using the returned Action
        void Execute(Func<Action> function);
    }

只是要在背景執行緒中執行的活動的一個陳述式,則第一個方法會。 第二個 < 動作 > 一個函式的方法是有趣多了。 我們來看看如何使用這個方法會通常在應用程式程式碼中。

第一次,假設您正在建構您 WinForms 或 WPF 的程式碼與模型檢視演講者模式的監督控制站表單。 (請參閱 msdn.microsoft.com/magazine/cc188690.aspx (如需詳細資訊在 MVP 模式)。在這種模型在 [主持人] 類別要將負責長時間執行的服務方法呼叫,並使用該傳回的資料來更新檢視]。 您新的主持人類別只會使用 ICommandExecutor 介面顯示先前處理的所有執行緒和執行緒封送處理工作,的[圖 5] 所示。

圖 5 的主持人類別]

public class Presenter
{
private readonly IView _view;
private readonly IService _service;
private readonly ICommandExecutor _executor;
public Presenter(IView view, IService service, ICommandExecutor
executor)
{
_view = view;
_service = service;
_executor = executor;
}
public void RefreshData()
{
_executor.Execute(() =>
{
var data = _service.FetchDataFromExtremelySlowServiceCall();
return () => _view.UpdateDisplay(data);
});
}
}

[主持人] 類別呼叫 ICommandExecutor.Execute 透過傳入傳回另一個區塊一個區塊中。 原始區塊叫用長時間執行服務呼叫,以取得部分的資料,並傳回訓練區塊可以用來更新使用者介面 (在這種情況下 IView)。 在這特定種情況下很重要而不是只更新 [IView 在同一時間,因為更新有封送處理回到使用者介面執行緒的使用訓練的方法。

圖 6 顯示 ICommandExecutor 介面的具體的實作。

圖 6 混凝土實作] 的 ICommandExecutor

public class AsynchronousExecutor : ICommandExecutor
{
private readonly SynchronizationContext _synchronizationContext;
private readonly List<BackgroundWorker> _workers =
new List<BackgroundWorker>();
public AsynchronousExecutor(SynchronizationContext
synchronizationContext)
{
_synchronizationContext = synchronizationContext;
}
public void Execute(Action action)
{
// Exception handling is omitted, but this design
// does allow for centralized exception handling
ThreadPool.QueueUserWorkItem(o => action());
}
public void Execute(Func<Action> function)
{
ThreadPool.QueueUserWorkItem(o =>
{
Action continuation = function();
_synchronizationContext.Send(x => continuation(), null);
});
}
}

(函式 < 動作 >) 的 Execute 方法叫用函式 < 動作 >在背景執行緒,然後會使用一個 SynchronizationContext 與訓練 (動作傳回的函式 < 動作 >) 物件主要使用者介面執行緒中執行 [訓練。

我喜歡傳遞到 ICommandExecutor 介面的區塊,因為如何小儀式的程式碼進行叫用背景處理。 在這種方法的較早 incarnation,我們 Lambda 運算式或匿名委派在 C# 中之前我有一個類似的實作,使用較少的命令模式類別,如下所示:

public interface ICommand
    {
        ICommand Execute();
    }

    public interface JeremysOldCommandExecutor
    {
        void Execute(ICommand command);
    }

先前的缺點是方法的我必須撰寫額外的命令類別,只是方法的為了建立背景作業和檢視更新的程式碼模型。 額外的類別宣告和建構函式的函式是多一點典禮程式碼,我們可以消除該功能的方法,但更重要是功能的方法可讓我所有此密切相關的程式碼置於單一位置,在主持人,而不是需要分散在這些較少的命令類別。

註解的接續傳遞樣式

建立訓練概念上,您可以使用傳遞訓練的程式設計樣式透過傳入一個訓練,而不是等候方法的傳回值的叫用方法。 接受訓練,方法是負責決定是否與呼叫 [訓練。

在 [我目前的 Web MVC 專案有數十種控制站的動作,從寄件者透過網域實體物件的 AJAX 呼叫用戶端瀏覽器的使用者輸入儲存更新。 大部分的這些控制站的動作只是叫用要儲存之變更的實體存放庫類別,但其他動作使用其他服務執行持續性的工作]。 (請參閱 msdn.microsoft.com/magazine/dd569757.aspx 存放庫類別的相關資訊在 MSDN Magazine 我文章中 4 月問題)。

這些控制站 」 動作的基本工作流程是一致的:

  1. 驗證網域實體,並記錄任何驗證錯誤。
  2. 如果驗證錯誤傳回指出作業失敗的用戶端的回應,並包含在用戶端上顯示驗證錯誤。
  3. 如果並未發生驗證錯誤存在網域實體,並傳回指示作業順利完成用戶端的回應。

我們會要做什麼時,會集中管理的基本工作流程,但仍允許每個個別控制站] 動作來變更實際的持續性是如何。 今天我的小組這樣的繼承自一個 CrudController < T >使用範本的方法,每一個子類別可以覆寫以新增或變更基本行為大量的超級類別。 這項策略也在第一個,工作出,但它快速分解為增加變化。 現在我的小組會移至使用藉由委派給下列介面類似我們控制站動作的訓練傳遞的樣式程式碼:

 

public interface IPersistor
    {
        CrudReport Persist<T>(T target, Action<T> saveAction);
        CrudReport Persist<T>(T target);
    }

在一般的控制站的動作會告訴 IPersistor 執行基本的 CRUD 工作流程,並提供 IPersistor 使用,以實際儲存目標物件一個訓練。 圖 7 顯示一個範例的控制站動作叫用 (Invoke) IPersistor,但使用不同的服務我們基本的存放庫比實際的持續性。

圖 7 的範例控制站執行

public class SolutionController
{
private readonly IPersistor _persistor;
private readonly IWorkflowService _service;
public SolutionController(IPersistor persistor, IWorkflowService
service)
{
_persistor = persistor;
_service = service;
}
// UpdateSolutionViewModel is a data bag with the user
// input from the browser
public CrudReport Create(UpdateSolutionViewModel update)
{
var solution = new Solution();
// Push the data from the incoming
// user request into the new Solution
// object
update.ToSolution(solution);
// Persist the new Solution object, if it's valid
return _persistor.Persist(solution, x => _service.Create(x));
}
}

我想要注意以下重要的是 IPersistor 本身決定是否及何時會呼叫提供 SolutionController [訓練。 圖 8 顯示 IPersistor 的一個範例的實作。

圖 8 的 IPersistor 的實作

public class Persistor : IPersistor
{
private readonly IValidator _validator;
private readonly IRepository _repository;
public Persistor(IValidator validator, IRepository repository)
{
_validator = validator;
_repository = repository;
}
public CrudReport Persist<T>(T target, Action<T> saveAction)
{
// Validate the "target" object and get a report of all
// validation errors
Notification notification = _validator.Validate(target);
// If the validation fails, do NOT save the object.
// Instead, return a CrudReport with the validation errors
// and the "success" flag set to false
if (!notification.IsValid())
{
return new CrudReport()
{
Notification = notification,
success = false
};
}
// Proceed to saving the target using the Continuation supplied
// by the client of IPersistor
saveAction(target);
// return a CrudReport denoting success
return new CrudReport()
{
success = true
};
}
public CrudReport Persist<T>(T target)
{
return Persist(target, x => _repository.Save(x));
}
}

撰寫的程式碼更少

坦白說,因為我想要學習更多有關功能的程式設計和如何它可以套用甚至內 C# 或 Visual Basic 的最初選擇本主題。 在撰寫本文的過程中我學許多相關程度很有用的功能程式設計技巧可以在一般的日常工作。 最重要的結論已達到,且什麼我試著與其他技術功能性程式設計方法的比較是這裡,傳達通常導致撰寫較少的程式碼和某些工作通常有多個宣告式程式碼 —,幾乎都是一件好事。

Jeremy MillerC# 是 Microsoft MVP 也是使用.NET 的相依性插入開放原始碼 StructureMap (structuremap.sourceforge.net) 工具和眼前 StoryTeller (storyteller.tigris.org) 工具 supercharged FIT 測試在.NET 中的作者。請造訪他網誌,[網底樹狀結構的開發人員,在 codebetter.com/blogs/jeremy.miller,CodeBetter 站台中的部分。