本文章是由機器翻譯。

程式設計師雜談

C# 樂趣多,第 2 部分

Ted Neward

Ted Neward歡迎回來。在我的上一篇專欄,"樂趣與 C#"(msdn.microsoft.com/magazine/dn754595),我簡要地談如何熟悉其他程式設計語言可以説明澄清你周圍可能原本看起來有些棘手的設計問題的思考。我介紹了幾年前在我被要求要對帳的交易記錄存儲本地與遠端存放事務理應相等清單清單諮詢訂婚期間極大的困難。我不得不要麼匹配他們的交易記錄金額 — — 什麼保證甚至確切的同一交易記錄匹配的 — — 或者標記生成清單中的不匹配的項。

我選擇使用 F # 為這項工作因為它是我熟悉的語言。坦白地說,它很容易本來應該像 Scala,Clojure 或 Haskell 的另一種語言。任何功能的語言會曾在類似的方式。這裡的關鍵不是語言本身或平臺,它跑了,但在函數式語言中涉及到的概念。這是一個相當功能友好問題。

F # 解決方案

只是為了重新審視,看 F # 中的解決方案圖 1 前看它將如何轉化成 C#。

看看前一列以查看中使用的 F # 語法簡要回顧圖 1,尤其是如果您不熟悉 F #。我也要去複習它,因為我把它變為 C# 中,所以你可能還剛剛開始。

圖 1 F # 解決方案來解決不同的交易記錄

type Transaction =
  {
    amount : float32;
    date : DateTime;
    comment : string
  }
type Register =
  | RegEntry of Transaction * Transaction
  | MissingRemote of Transaction
  | MissingLocal of Transaction
let reconcile (local : Transaction list) 
  (remote : Transaction list) : Register list =
  let rec reconcileInternal outputSoFar local remote =
    match (local, remote) with
    | [], _
    | _, [] -> outputSoFar
    | loc :: locTail, rem :: remTail ->
      match (loc.amount, rem.amount) with
      | (locAmt, remAmt) when locAmt = remAmt ->
        reconcileInternal (RegEntry(loc, rem) :: 
          outputSoFar) locTail remTail
      | (locAmt, remAmt) when locAmt < remAmt ->
        reconcileInternal (MissingRemote(loc) :: 
          outputSoFar) locTail remote
      | (locAmt, remAmt) when locAmt > remAmt ->
        reconcileInternal (MissingLocal(rem) :: 
          outputSoFar) local remTail
      | _ ->
        failwith "How is this possible?"
  reconcileInternal [] local remote

C# 解決方案

在起始點,您需要的交易和登記冊的類型。交易記錄類型是很容易的。它是一個簡單的結構類型與三個命名的元素,使其易於模型作為一個 C# 類:

class Transaction
{
  public float Amount { get; set; }
  public DateTime Date { get; set; }
  public String Comment { get; set; }
}

這些自動屬性可使此類幾乎和其表弟 F # 盡可能短。老實說,如果我要真的把它做了什麼的 F # 版本聖經翻譯成一對一,我應該介紹重寫的 Equals,從此方法。為了本專欄中,雖然,這將工作。

事情變得棘手與可區分聯合註冊類型。像在 C# 中的枚舉,註冊類型的實例只能是三個可能值之一 (RegEntry、 MissingLocal 或缺失­遠端)。與 C# 枚舉,不同的是每個這些值又可以包含資料 (RegEntry,匹配的兩個交易記錄或 MissingLocal 或失蹤的失蹤事務­遠端)。雖然它很容易在 C# 中創建三個不同的類,這三個類必須以某種方式相關。我們需要一個清單,可以包含任何三個 — — 但只有這三個 — — 為返回的輸出,如圖所示,在圖 2。你好,繼承。

圖 2 使用繼承,以包含三個不同的類

class Register { }
  class RegEntry : Register
  {
    public Transaction Local { get; set; }
    public Transaction Remote { get; set; }
  }
  class MissingLocal : Register
  {
    public Transaction Transaction { get; set; }
  }
  class MissingRemote : Register
  {
    public Transaction Transaction { get; set; }
  }

它是可笑不復雜,只是更詳細。如果這是生產面向代碼,有幾個更多的方法,我應該補充 — — 等於,從此,幾乎可以肯定,ToString。雖然可能有幾種方法可以使它更地道 C#,我會寫信與 F # 的啟示相當接近的協調方法。以後我要去找地道的優化。

F # 版本具有"外,"可公開訪問的功能重現­地叫進內,封裝函數。然而,C# 具有嵌套方法沒有概念。最近的我可以近似是用兩種方法 — — 一個聲明為公共的和一個私密金鑰。即便如此,這並不完全相同。在 F # 版本中,嵌套的函數是從每一個人,甚至其他同一模組中的函數封裝的。但這是的最好的我們可以得到,正如你可以看到在圖 3

圖 3 嵌套的函數封裝在這裡

class Program
{
  static List<Register> ReconcileInternal(List<Register> Output,
             List<Transaction> local,
             List<Transaction> remote)
  {
    // . . .
  }
  static List<Register> Reconcile(List<Transaction> local,
             List<Transaction> remote)
  {
    return ReconcileInternal(new List<Register>(), local, remote);
  }
}

作為邊注,我現在可以完成"隱藏的每個人都"通過完全作為內部協調的本地變數引用 lambda 運算式作為編寫的內建函式的遞迴方法。也就是說,這就是大概少太多的俯首到原來的和完全不地道到 C#。

它不是大多數 C# 開發人員可以做的但它會有 F # 版本的效果幾乎相同。內部協調­內部,我要明確地提取使用資料元素。然後我明確地把它寫在 if/其他如果樹中,而不是更簡潔、 更簡潔 F # 模式匹配。然而,它真的是完全相同的代碼。如果本地或遠端的清單是空的我做遞迴。只返回輸出和收工,就像這樣:

static List<Register> ReconcileInternal(List<Register> Output,
              List<Transaction> local,
              List<Transaction> remote)
{
  if (local.Count == 0)
    return Output;
  if (remote.Count == 0)
    return Output;

然後,我需要提取每個清單中的"團長"。我還需要繼續對剩餘的每個清單 ("尾巴") 的引用:

Transaction loc = local.First();
List<Transaction> locTail = local.GetRange(1, local.Count - 1);
Transaction rem = remote.First();
List<Transaction> remTail = remote.GetRange(1, remote.Count - 1);

這是一個地方,在那裡我可以介紹一個巨大的性能損失,如果我不小心。清單是在 F # 中,不可變的所以以一個清單的尾部僅僅接受對第二項的引用清單中。不製作的任何副本。

C# 中,然而,有沒有這樣的保證。這意味著最終的結果可能使完整副本清單中的每一次。GetRange 方法表示,它使得"淺拷貝",意味著它將創建一個新的清單。然而,它將指向原始交易元素。這可能是的最好的我希望不會太異國情調了。儘管如此,是否代碼將成為一個瓶頸,得到必要時一樣具有異國風情。

再來看的 F # 版本,我真的研究中第二個模式匹配是金額在本地和遠端的交易中,如中所示圖 4。所以我提取,以及這些值,開始比較它們。

圖 4 F # 版本檢查的本地和遠端的數額

 

float locAmt = loc.Amount;
  float remAmt = rem.Amount;
  if (locAmt == remAmt)
  {
    Output.Add(new RegEntry() { Local = loc, Remote = rem });
    return ReconcileInternal(Output, locTail, remTail);
  }
  else if (locAmt < remAmt)
  {
    Output.Add(new MissingRemote() { Transaction = loc });
    return ReconcileInternal(Output, locTail, remote);
  }
  else if (locAmt > remAmt)
  {
    Output.Add(new MissingLocal() { Transaction = rem });
    return ReconcileInternal(Output, local, remTail);
  }
  else
    throw new Exception("How is this possible?");
}

每個分支是樹的很容易理解這一點。我將新元素添加到輸出清單,然後遞迴處理清單元素的本地和遠端處理。

總結

如果 C# 解決方案是真的這優雅的何必放在第一位考慮通過 F # 停止呢?很難解釋,除非你經歷相同的過程。從根本上說,通過將 F # 放在第一位有血有肉的演算法停止。這我第一次嘗試是一個絕對的災難。我開始通過使用雙"foreach"迴圈的兩個清單進行反覆運算。我試著跟蹤的狀態一路走來,和告終巨大、 蒸的混亂,我將永遠不會已經能夠在 100 萬的年中調試。

學習如何"以不同的方式思考"(要借用一下從幾十年前的一個著名的電腦公司行銷線) 生成的結果,不是語言本身的選擇。我都能輕鬆地告訴經歷 Scala,Haskell 或 Clojure 這個故事。這一點並不是語言的功能集,但大多數函數式語言背後的概念 — — 遞迴,特別是。這是什麼説明突破心理的僵局。

這是一部分的原因,開發人員應該學習一種新的程式設計語言作為每一年,首先提出了一個務實的程式師,戴夫Thomas,Ruby 的名聲。你的頭腦不能説明,但受到新思想和新的選擇。類似各種各樣的想法出現時,一個程式師花一些時間與計畫,口齒不清或使用基於堆疊的語言 (如第四 — — 或用一種類似 Io 的基於原型的語言。

如果你想輕簡介全部關閉 Microsoft.net 平臺的不同語言的數目,我強烈推薦布魯斯 · 泰特的書,"七種語言在七個星期"(務實書架,2010年)。你不能直接在.NET 平臺上用其中一部分。然後再一次,有時這場勝利是我們如何看待這一問題和框架的解決方案,不一定可重用的代碼。您編碼愉快 !


Ted Neward 是 iTrellis,一家諮詢服務公司的 CTO。他已經寫了超過 100 篇文章和撰寫或合作撰寫十余本書籍,包括"專業 F # 2.0" (Wrox,2010年)。他是 C# MVP,在世界各地的會議上講話。他提供諮詢和指導定期 — — 達到他在 ted@tedneward.comted@itrellis.com 如果你感興趣讓他來與您的團隊工作,讀他的博客 blogs.tedneward.com

感謝以下的微軟技術專家對本文的審閱:林肯阿特金森