程式設計師雜談

透過 Gemini 程式庫追求動態

Ted Neward

Ted Neward我的文章或博客的讀者將會知道這已經,但對於那些闖進了這篇文章 (是否由事故或因為他們覺得這是星座),我往往要花很多時間去看其他語言和平臺。 這通常是為了説明模型軟體,更有效地、 高效率地或準確的想法或概念。

最近有一個趨勢 — — 儘管它並不是所有最近 — — 內 Web 社區一直追求的"動態"或"腳本"的語言,特別是其中兩個:紅寶石 (Ruby on Rails 框架,有時也稱為 RoR,編寫所針對的) 和 JavaScript (我們具有的 Node.js 執行伺服器端的應用程式,有數以百計的框架)。這兩種語言都缺乏什麼我們習慣了在C# 和Visual Basic世界的特徵:嚴格遵守哪些類定義作為唯一定義哪些物件組成。

在 JavaScript (有時自作聰明主持人像我自己作為"Lisp 用大括弧"的特點是語言),例如,物件是一個完全可變的實體,意味著您可以在需要時 (或想要) 添加屬性或方法:

var myCar = new Object();
myCar.make = "Ford";
myCar.model = "Mustang";
myCar.year = 1969;
myCar.makeSounds = function () {
  console.log("Vroom!
Vroom!")
}

物件 myCar,首先構造時,對它有沒有屬性或方法 — — 隱式添加這些時的資料值 ("Ford,""野馬",1969年和函數) 都設置為那些名字 (廠商、 型號、 年和 makeSounds)。 本質上,每個在 JavaScript 中的物件是只是一本字典的名稱/值對,凡對的值可以是要調用的函數或資料元素。 除語言設計者,玩這樣的類型資訊的能力,通常稱為元物件協定 (澳門幣),和約束的子集,這通常稱為面向方面程式設計 (AOP)。 它是靈活、 強大的物件,但這是非常不同的 C# 方法。 而是比嘗試創建複雜的類層次結構,在其中您嘗試捕獲每個可能的變化,通過繼承,作為傳統的 C# 物件設計建議,議定書 》 的做法說真實世界中的事物不所有完全相同的 (除了其資料的課程),並在其中你示範他們的方式不應該。

開發者社區 Microsoft.NET 框架的一部分已經許多年現在會記得較早版本的 C# 引入動態關鍵字/類型,允許您對物件的引用在運行時發現其成員的聲明,但這是一個不同的問題。 (動態功能集使容易寫反思樣式代碼,不創建物件的拖把種類)。幸運的是,C# 開發人員有兩個選項:傳統的靜態類型定義,通過標準的 C# 類設計機制 ; 或靈活的類型定義,通過開放原始碼庫調用生成動態的功能,以使你附近 JavaScriptian 功能上的雙子座。

雙子座的基本知識

好像很多,我在本專欄中討論過的套裝軟體,雙子是通過 NuGet 可用:封裝管理員主控台中的"安裝套裝軟體雙子星"到您的專案帶來了善良。 不像其他套裝軟體你已經見過,不過,雙子座入專案安裝時它不帶大會或兩個 (或三個以上)。 相反,它帶來了幾個原始程式碼檔並將它們放入一個叫做"橡木"資料夾中並直接向專案中添加它們。 (寫這篇文章,雙子座 1.2.7 包含四個檔:Gemini.cs、 GeminiInfo.cs、 ObjectExtensions.cs 和一個文字檔,其中包含的版本資訊)該資料夾命名為橡木的原因是實際上非常合理:雙子座是實際上的子集 (不令人驚訝的是,稱為橡木) 的較大專案帶來很多這個動態程式設計善良ASP.NETMVC 世界 — — 大橡木包在以後的專欄中,我將探討。

自行確定事實那雙子星座交付作為源真的沒什麼大不了 — — 生活在自己的命名空間 (橡木) 的代碼,和將簡單地被編譯成專案的原始程式碼檔的其餘部分是。 實際一點,然而,有原始程式碼檔便於荒謬地逐句通過雙子座的原始程式碼時出了差錯,或甚至只是為了看看什麼是可用的因為IntelliSense有時完全被擊敗的動態關鍵字類型使用的代碼通過拇指。

快速入門

再次,正如我的習慣,我開始通過創建單元測試專案,用來寫一些勘探測試 ; 到該專案安裝雙子並測試它外面通過創建一個簡單的"你好的世界"-像測試:

[TestMethod]
public void CanISetAndGetProperties()
{
  dynamic person = new Gemini(
    new { FirstName = "Ted", LastName = "Neward" });
  Assert.AreEqual(person.FirstName, "Ted");
  Assert.AreEqual(person.LastName, "Neward");
}

發生了什麼在這裡微妙的但功能強大:雙子星座,在另一邊的"人"的引用,該物件是一個類型,直到分配到 (如前面的代碼的情況下) 或顯式添加到物件中的方法,SetMember 和 GetMember,通過這些成員是基本上是空的所有的屬性或方法,就像這樣:

[TestMethod]
public void CanISetAndGetPropertiesDifferentWays()
{
  dynamic person = new Gemini(
    new { FirstName = "Ted", LastName = "Neward" });
  Assert.AreEqual(person.FirstName, "Ted");
  Assert.AreEqual(person.LastName, "Neward");
  person = new Gemini();
  person.SetMember("FirstName", "Ted");
  person.SetMember("LastName", "Neward");
  Assert.AreEqual(person.GetMember("FirstName"), "Ted");
  Assert.AreEqual(person.GetMember("LastName"), "Neward");
}

雖然我這樣做,這裡的資料成員,也是同樣容易做這行為成員 (即,方法),通過將它們設置為 DynamicMethod (它返回 void) 或動態的實例­函數 (該公司預計傳回值),其中每個不帶參數。 或者您可以設置它們向其夥伴"WithParam",如果該方法或函數可以帶參數,就像這樣:

[TestMethod]
public void MakeNoise()
{
  dynamic person =
    new Gemini(new { FirstName = "Ted", LastName = "Neward" });
  person.MakeNoise =
    new DynamicFunction(() => "Uh, is this thing on?");
  person.Greet =
    new DynamicFunctionWithParam(name => "Howdy, " + name);
    Assert.IsTrue(person.MakeNoise().Contains("this thing"));
}

一個有趣的趣聞升起出雙子圖書館,順便說:雙子座 (沒有任何的替代執行) 的物件使用"結構性鍵入"以確定他們是否相等,或它們是否滿足特定的實現。 相反 OOP 類型系統,使用繼承/IS-A 測試來確定給定的物件是否可以滿足物件參數類型上的限制,結構上類型化的系統反而只問中傳遞的物件是否具有所有要求 (在這種情況下,成員) 使代碼正常運行所需。 結構性打字、 之間的功能的語言,是眾所周知還要"鴨子類型化"一詞在動態語言 (但這不會是個很酷的聲音)。

一會兒,考慮採用物件並列印出友好的消息,有關該物件,如圖中所示的方法, 圖 1

圖 1 使用方法的物件,並且列印出一條消息

string SayHello(dynamic thing)
{
  return String.Format("Hello, {0}, you are {1} years old!",
    thing.FirstName, thing.Age);
}
[TestMethod]
public void DemonstrateStructuralTyping()
{
  dynamic person = new Gemini(
    new { FirstName = "Ted", LastName = 
      "Neward", Age = 42 });
    string message = SayHello(person);
    Assert.AreEqual("Hello, Ted, you are 42 years old!", 
      message);
    dynamic pet = new Gemini(
      new { FirstName = "Scooter", Age = 3, Hunter = true });
  string otherMessage = SayHello(pet);
  Assert.AreEqual("Hello, Scooter, you are 3 years old!", 
      otherMessage);
}

通常情況下,在傳統物件導向層次結構中,人和寵物將很可能來自非常不同的繼承樹的分支 — — 人和寵物不一般共用大量的共同屬性 (儘管貓所認為的) 軟體系統中。 在結構上或鴨鍵入系統,然而,較少的工作需要去做深和無所不包的繼承鏈 — — 如果有人類那也狩獵,然後嘿,人權上有一個"獵人"成員和任何想要檢查的物件的獵人狀態的常式傳遞中可以使用該成員,無論是一個人、 貓或捕食者無人機。

審訊

權衡鴨打字的辦法,許多人會注意到,是編譯器不能執行,只可以通過某些種類的物件中,並且也是如此的雙子座類型 — — 尤其是因為大多數雙子座代碼慣用法上存儲的物件背後的動態引用。 您需要帶一點額外的時間和努力,以確保被移交的物件滿足要求,否則,面對一些運行時異常。 這意味著質問的物件來看看它有沒有必要的成員,雙子座在完成使用 RespondsTo 方法 ; 也有一些方法返回的雙子座承認作為一個給定物件的一部分的各個成員。

例如,請考慮需要知道如何獵取的物件的方法:

int Hunt(dynamic thing)
{
  return thing.Hunt();
}

當踏板車中傳遞時,東西做工精細,如圖所示,在圖 2

圖 2 當它工作時的動態程式設計

[TestMethod]
public void AHuntingWeWillGo()
{
  dynamic pet = new Gemini(
    new
    {
      FirstName = "Scooter",
      Age = 3,
      Hunter = true,
      Hunt = new DynamicFunction(() => new Random().Next(4))
    });
  int hunted = Hunt(pet);
  Assert.IsTrue(hunted >= 0 && hunted < 4);
  // ...
}

但不知道如何狩獵的東西傳遞時,異常將導致,如圖所示,在圖 3

圖 3 動態程式設計失敗時

[TestMethod]
public void AHuntingWeWillGo()
{
  // ...
dynamic person = new Gemini(
    new
    {
      FirstName = "Ted",
      LastName = "Neward",
      Age = 42
    });
  hunted = Hunt(person);
  Assert.IsTrue(hunted >= 0 && hunted < 4);
}

為防止這種情況,狩獵方法應該測試通過使用 RespondsTo 方法存在問題的成員。 這是簡單的包裝,周圍的 TryGetMember 方法,用於簡單的布林值 yes/no 的反應:

int Hunt(dynamic thing)
{
  if (thing.RespondsTo("Hunt"))
    return thing.Hunt();
  else
    // If you don't know how to hunt, you probably can't catch anything.
return 0;
}

順便說一句如果這一切都看起來相當簡單的樣板或字典 < 物件,字串 > 不是不正確的評估的包裝 — — 雙子座類的基礎是,確切的字典介面。 但包裝類型有助於緩解某些類型系統波動,否則是必要的如不會使用 dynamic 關鍵字。

但當幾個物件共用類似的行為時,會發生什麼呢? 例如,四隻貓都知道如何打獵,和它將是比較低效的要寫一個新的匿名方法定義為所有四,尤其是作為所有四個份額貓科動物狩獵本能。 在傳統的 OOP 中這不是一個問題,因為他們會全部被貓類的成員,因此共用相同的實現。 在澳門幣系統,如 JavaScript,通常是一種機制允許物件將推遲或"鏈"屬性或請求調用另一個物件中,稱為"原型"。雙子座在您使用有趣的組合靜態打字和拖把稱為"擴展"。

Prototype

首先,您需要標識貓的基本類型:

public class Cat : Gemini
{
  public Cat() : base() { }
  public Cat(string name) : base(new { FirstName = name }) { }
}

請注意,貓類繼承雙子星座,這是什麼會使貓類也到目前為止已討論的所有動態的靈活性 — — 事實上,貓第二個建構函式使用同一雙子座建構函式被用來創建動態的所有實例,到目前為止。 這意味著所有的前面的散文仍持有任何貓的實例。

但雙子還允許我們的貓可以如何申報"擴展",這樣,每一隻貓收益相同的功能,而無需顯式地將它添加到每個實例。

擴充類別

為此功能的實際使用,認為這就是正在開發的 Web 應用程式。 通常情況下,你需要對 HTML-­轉義名稱值被存儲和返回,為了避免無意中允許 HTML 注射 (或者更糟,SQL 注入):

string Htmlize(string incoming)
{
  string temp = incoming;
  temp = temp.Replace("&", "&amp;");
  temp = temp.Replace("<", "&lt;");
  temp = temp.Replace(">", "&gt;");
  return temp;
}

這是一種痛苦 ; 系統中定義的每個模型物件上都要記住 幸運的是,拖把允許您系統地"中達到"和定義行為的新成員上的模型物件,如圖所示,在圖 4

圖 4 寫作方法,而無需編寫方法

[TestMethod]
public void HtmlizeKittyNames()
{
  Gemini.Extend<Cat>(cat =>
  {
    cat.MakeNoise = new DynamicFunction(() => "Meow");
    cat.Hunt = new DynamicFunction(() => new Random().Next(4));
    var members =
      (cat.HashOfProperties() as IDictionary<string, object>).ToList();
    members.ForEach(keyValuePair =>
    {
      cat.SetMember(keyValuePair.Key + "Html",
        new DynamicFunction( () =>
          Htmlize(cat.GetMember(keyValuePair.Key))));
    });
  });
  dynamic scooter = new Cat("Sco<tag>oter");
  Assert.AreEqual("Sco<tag>oter", scooter.FirstName);
  Assert.AreEqual("Sco&lt;tag&gt;oter", scooter.FirstNameHtml());
}

基本上,擴展調用添加到每個貓類型,尾碼"Html",所以 FirstName 屬性可以訪問 HTML 安全版本中,通過調用 FirstNameHtml 方法相反的新方法。

這是可以做到完全在運行時為任何雙子星座-­繼承類型在系統中。

持久性和更多

雙子座並不打算和一群的動態解析物件代替 C# 環境的整體 — — 遠非如此。 雙子座在其首頁的用法,在橡樹 MVC 框架,用於添加到模型 (尤其) 類的持久性和其他有用的行為,並無擁塞,使用者代碼或分部類中添加驗證。 然而,即使在橡木,雙子座表示一些功能強大的設計力學,其中有些讀者可能還記得 8 系列的一部分我多模式化的.NET 從一段時間了 (msdn.microsoft.com/magazine/hh205754)。

所以說的橡木,那下一次是在水龍頭上,看看如何所有這個動態的東西在真實世界的情況下發揮作用。

快樂的編碼 !

Ted Neward 是校長與 Neward & 同夥 LLC。他已寫了 100 多個文章和創作和合著十多本書,包括"專業 F # 2.0"(Wrox 2010)。他是 F # MVP 和注意到JAVA專家,並在世界各地的JAVA和.NET 會議上講話。他諮詢和週期性導師 — — 達到他在 ted@tedneward.comTed.Neward@neudesic.com 如果你感興趣了他來與您的團隊工作。在他的博客 blogs.tedneward.com 可以跟隨在 Twitter 上和 twitter.com/tedneward

感謝以下技術專家對本文的審閱:埃米爾 Rajan (改善企業)
埃米爾 Rajan 是改善企業的主要顧問。 他是社會發展的一個積極成員和具有專門知識的ASP.NETMVC,HTML5,其餘結構、 紅寶石、 JavaScript/CoffeeScript、 NodeJS,iOS/ObjectiveC 和 F #。 Rajan 是真正通曉多種語言軟體堅定不移的激情。 他是在 Twitter 上 @amirrajan 和在網頁上 github.com/amirrajanamirrajan.netimprovingenterprises.com