本文章是由機器翻譯。
程式設計師雜談
多範型 .NET,第 7 部分:參數化的中繼程式設計
Ted Neward
當我小的大學學生,充滿 calculus 的 microeconomics 上課教授共用工作指導書 dom resonate 某些的字跟我到這一天:
"如果同時進入我們所選擇的冗長細節,你發現自己無法看到的理由為什麼我們要進入這些繁瑣的詳細資訊,您的責任是主題的打斷我的話,說,' 教授安德森,點是主題的什麼?我們要回去,並解釋如何我們這兒的片刻。
已經坐在逐一本系列中的文件可能表現的讀者偷襲那道牆,因此我們需要返回先前畫面並檢閱如何我們這裡有一些時間。
重點
本質依所述詹姆斯 Coplien c +"Multi-Paradigm 設計為 +"(Addison-Wesley,1998年),其中想到了這一點很多這一系列的文章中書寫,他活頁簿中所有程式設計都是的一種擷取的共同點 — 撰寫代表 「 一直 」 案例的程式碼,並讓它的行為或以不同方式結構在某些情況下在語言內再使用 [成績的變異性建構。
物件導向程式設計,比方說,擷取的共同點成類別,然後允許透過繼承,改變該共同點的子類別建立的變化。通常,是類別的藉由變更行為的使用方法或訊息,視語言有問題的特定部份。專案的共同點和變化需求不一定符合物件導向開發架構因此整齊,不過,或任何其他特定單一架構,該項目的 — 如嘗試提供變化該程序性程式設計無法輕易地擷取成長超出程序性程式設計物件導向程式設計。
幸運的是基於本雜誌讀者,在 Visual Studio 2010 Microsoft 所提供的語言是 multiparadigmatic 的語言,也就是說它們繪製數個不同的程式撰寫範例一起為單一的語言。Coplien 先識別 c + + 因此 multiparadigmatic 的語言中,在於它整合三個主要的範例:程序,物件和 metaprogrammatic (有時也更精確地稱為 metaobject)。C + + 也廣泛已為複雜語言,太困難平均母片上,開發人員多半是因為真的很難何時使用各種功能的語言來解決特定問題,請參閱 criticized。
現代程式語言卻經常開發成高度 multiparadigmatic 的語言。F #、 C# 和 Visual Basic 中,為的 Visual Studio 2010,請直接支援五個這類範例:程序、 物件導向、 metaobject,動態且正常運作。這三種語言 — 四,如果您將包含 C + + /cli CLI 該混合中的 — 因此冒著落入 c + + 為相同的命運。
沒有清楚的每個這些語言已混合的架構,開發人員可以輕鬆地進攻的最愛的功能設陷,開發人員太多依賴一個功能或要排除其他的開發架構,最後建立過於複雜的程式碼,最後取得把並重新寫入的位置。這樣做太多時間,並且語言開始忍受的開發人員挫敗,通往最後呼叫的新語言中或只是退休一般 brunt。
程序和物件導向開發架構
到目前為止,我們看到套用至程序或結構化程式設計,移位,其中我們擷取資料結構的共同點,並於其中運作這些結構餵食成不同的程序的呼叫,藉由建立新作業的程序呼叫這些相同的資料結構上建立變化的共同點/變化性分析。我們也看到的物件,我們的共同點擷取成類別和子類別化這些的類別,並變更位元和部分它們透過覆寫方法或屬性,以建立變化周圍的共同點/變化性。
謹記,也可,因為繼承 (大多數的情況) 僅准許您進行正變化,此時冒出另一個問題,我們無法移除項目自基底類別,例如成員方法或欄位。在 CLR,我們可以藉由遮蔽 (使用 virtual 關鍵字的覆寫關鍵字在 C# 中,而不是為例) 隱藏衍生的可存取成員實作。不過,這表示其他,不將它完全移除項目以取代它的行為。欄位不會出現無論如何。
這個觀察某些造成干擾無法置之不理:物件無法執行我們所需的一切 — 至少不單純的物件。比方說,物件不能擷取沿著結構使用繼承的變化:集合類別的擷取堆疊類似行為但不同的不同資料型別 (依此類推字串整數、 雙精度浮點數,) 無法擷取該結構的差異。授與,CLR 在我們可以使用統一的型別系統。我們可以儲存參考執行個體的 System.Object 和向下轉型為有必要,但這不是能夠建立的型別,會儲存只有一個型別相同。
這個解析我們來到了之物件的 metaobject 程式設計,因為我們要尋找的方式來擷取傳統物件座標軸之外的動作。
第一次這類中繼的方法是 generative,原始碼產生的移位,其中以某種範本為基礎。這種方法可以讓在不同的兩軸的各種上一些變化,但為有限 (大多數的情況) 的來源時間執行模型。它也會開始瓦解 variabilities 數目上升因為來源範本已經以某種方式會不同 (通常與範本語言內隱藏決策的陳述式) 產生的程式碼在程式碼產生階段,以及這可以在範本中建立的複雜性。
第二個這類中繼方法是程式設計的,反射型或 attributive。在執行階段,程式碼會使用平台 (反映) 的完整的精確度中繼資料功能來檢查程式碼,並以不同的方式為基礎所它看到那里的行為。
這允許彈性的實作或執行階段決策,周圍的行為,但導入其本身的限制:沒有型別關聯性有反射/attributive 設計,這表示沒有任何方法可以用程式設計的方式確保 [只有資料庫永久性的型別可以傳遞至方法,比方說,相對於 XML 永久性的型別中。缺乏繼承或其他關聯性表示特定數量的型別安全 (以及因此是重要的能力,若要避免錯誤) 會因此遺失。
這便讓我們第三個 metaobject 設備中。NET 環境:參數的多型。這表示能夠定義具有類型做為參數型別。或者,更簡單,讓 Microsoft。NET Framework 會參考為泛型。
泛型
在最簡單的形式,泛用允許具有組件在編譯時期,從用戶端程式碼所提供其結構的型別產生編譯時期建立。換句話說,堆疊行為集合的開發人員不需要在知道他程式庫時編譯何種型別其用戶端可能會想要將儲存在不同的執行個體 — 它們在建立集合的執行個體時提供該資訊。
例如,在前一篇文章,我們看到笛卡兒點型別的定義需要 (X 和 Y) 座標軸值的表示 (所負責部分點開發人員) 前的時間決定。他們應該是整數值嗎?應該允許它們可以是負數吗?
在數學領域中使用的笛卡兒點可能很好必須是浮點數和負。用來代表一個像素的圖形畫面必須為正數、 整數類資料且某一數字範圍內,可能是因為電腦顯示的 4 億的億不是的電腦但已經司空見慣笛卡兒點。
因此,在它的介面中,井設計笛卡兒點文件庫將會需要幾個不同的點類型:為 x 使用不帶正負號的位元組,y 的欄位,一個使用增加一倍為 x 及 Y] 欄位,以此類推。其行為,大多數的情況下,將會相同所有這些型別,清楚地反白顯示想来擷取的共同點的違規情形 (已知的 colloquially 乾一般的原則:"不要重複自行")。
使用參數的多型,我們就可以擷取該共同點相當能夠恰好置入:
class Point2D<T> {
public Point2D(T x, T y) { this.X = x; this.Y = y; }
public T X { get; private set; }
public T Y { get; private set; }
// Other methods left to the reader's imagination
}
現在開發人員可以指定精確的他想要使用的笛卡兒點的範圍和型別] 屬性。 當使用數學的網域,他建立執行個體的 Point2D <double> 值,當您使用這些值顯示的螢幕,他會建立 Point2D 的執行個體,並 <sbyte> Point2D 或 <ushort>。 每個其本身的區分性類型,所以嘗試比較或指派 Point2D <sbyte> 以 Point2D <double> 將會一敗塗地在編譯時期,完全依照偏好的強型別的語言。
寫入時,不過,Point2D 型別仍然有一些缺點。 我們已捕捉的共同點笛卡兒點,當然,但是我們已經基本上允許任何種類的值用於 x 和 y 值。 雖然這應該被視為在某些案例中很有用 ("此圖形中,我們正在圖表評等每個人提供特定電影 」),一般的規則,嘗試建立 Point2D <DateTime> 可能會令人困擾的而且想要建立 Point2D <System.Windows.Forms.Form> 幾乎是無庸置疑。 我們要介紹某種這裡負變化性 (或,如果您想要的話,節流正變化的程度),限制哪些可以是在 Point2D 中的座標軸值的型別。
許多。NET 語言擷取透過參數化條件約束這個負變化性 — 有時也稱為型別條件約束,必須要有型別參數的明確地描述條件:
class Point2D<T> where T : struct {
public Point2D(T x, T y) { this.X = x; this.Y = y; }
public T X { get; private set; }
public T Y { get; private set; }
// Other methods left to the reader's imagination
}
這表示編譯器不接受任何項目不是實值型別 t。
若要老實說,這並不完全負的變化,事實上,但它做為其中一個相較於嘗試移除特定功能,只會約略估計相當多的何種,則為 true 負變化會問題。
不同的行為
參數的多型通常用來提供結構座標軸上的變化,但身為 c + + Boost 程式庫所示範的開發人員,它不是它可以沿著其作業的唯一座標軸。 明智地使用型別條件約束,我們也可以使用泛用來提供原則的機制,用戶端可以在其中指定所要建構的物件的行為機制。
一會兒,考慮傳統問題的診斷記錄:為了幫助診斷伺服器上 (或甚至在用戶端電腦上) 中執行程式碼的問題,我們要追蹤的透過程式碼基底的程式碼執行。 這通常是指郵件寫入檔案。 但是有時候我們想要讓郵件出現在主控台中,至少某些案例中,而且有時候我們想遭捨棄的訊息。 處理診斷記錄訊息已經棘手問題這些年來,而且已經提議過各種解決方案。 提升的課程提供新的方法。
我們一開始先定義介面:
interface ILoggerPolicy {
void Log(string msg);
}
這是簡單的介面,與定義的行為我們想要不盡相同,我們沒有透過一系列子型別,該介面的一或多個方法:
class ConsoleLogger : ILoggerPolicy {
public void Log(string msg) { Console.WriteLine(msg); }
}
class NullLogger : ILoggerPolicy {
public void Log(string msg) { }
}
這裡我們有兩種可能的實作,其中將記錄檔訊息寫入主控台時擲另出該離開。
使用此要求用戶端在選擇的方法是宣告為型別參數,記錄器建立執行個體,以執行實際的記錄:
class Person<A> where A : ILoggerPolicy, new() {
public Person(string fn, string ln, int a) {
this.FirstName = fn; this.LastName = ln; this.Age = a;
logger.Log("Constructing Person instance");
}
public string FirstName { get; private set; }
public string LastName { get; private set; }
public int Age { get; private set; }
private A logger = new A();
}
描述要使用,然後的記錄器類型很容易的事的將建構函式時間參數,傳遞像這樣:
Person<ConsoleLogger> ted =
new Person<ConsoleLogger>("Ted", "Neward", 40);
var anotherTed =
new Person<NullLogger>("Ted", "Neward", 40);
這個機制可以讓程式開發人員建立它們自己的自訂記錄實作,並插入來供人 < > 沒有人 < > 的執行個體 開發人員不必知道使用的記錄任何的實作詳細資料。 但是執行許多其他方法也這項操作,例如記錄器欄位或傳入記錄器的屬性,必須從外部的執行個體 (或取得透過相依性的插入方法)。 泛型為主的方法有一個優點是不是欄位為基礎的方法,不過,這是編譯時期區別:個人 <ConsoleLogger> 是來自 <NullLogger> 的人的不同且各自獨立型別。
金錢,金錢金錢
Plagues 開發人員的一個問題是不被 quantified 的單位數量並無用處。 倍數來表示一分的單位不清楚為 1000 的馬匹或 1000 員工或 1000 比薩相同的動作。 可是,只是更為清楚,1000 一分的單位和 10 元是,事實上,相同的值。
這就成為數學計算來擷取單位 (度/弧度,英呎/公尺華氏/攝氏) 的需求就更加重要,特別是如果您正在編寫指南控制軟體的非常大的火箭甚至更為不可或缺的。 請考慮 Ariane 5,其 maiden 飛行得在場才能 self-destructed 轉換中發生錯誤。 或火星,其中 slammed 到 Martian NASA 探查全速轉換錯誤受限於橫印。
最近,像 F # 的新語言所決定 acclimate 直接語言的功能,度量單位,但即使 C# 和 Visual Basic 可以做類似種類事物,多虧了為泛型。
Channeling 我們內部 ‧ Fowler,讓我們從開始知道數量 (數量) 和特定的貨幣金額的貨幣 (型別) 的簡單金錢類別:
class Money {
public float Quantity { get; set; }
public string Currency { get; set; }
}
在介面中,這看起來可行,但之前太久我們會想要開始進行類似值的項目,例如將 Money 執行個體放在一起 (很常見的作業與金錢,當您想想看):
class Money {
public float Quantity { get; set; }
public string Currency { get; set; }
public static Money operator +(Money lhs, Money rhs) {
return new Money() {
Quantity = lhs.Quantity + rhs.Quantity, Currency = lhs.Currency };
}
}
當然,問題會發生當我們嘗試新增美國 歐洲歐元 (EUR) 字串,例如當我們出去去吃午餐和金錢 (USD) (畢竟,所有人都知道歐洲人 brew 最佳的啤酒,但是美國人,使最佳比薩):
var pizza = new Money() {
Quantity = 4.99f, Currency = "USD" };
var beer = new Money() {
Quantity = 3.5f, Currency = "EUR" };
var lunch = pizza + beer;
採用一下財務儀表板的人想要實現有人會在取得擷取 — 歐元轉換成 1-1 比例的金額。 若要防止意外詐騙,我們可能想要確定編譯器知道不將轉換成 EUR 的 USD,而不需要再查閱目前轉換率核准的轉換程序 (請參閱圖 1)。
圖 1轉換是在順序
class USD { }
class EUR { }
class Money<C> {
public float Quantity { get; set; }
public C Currency { get; set; }
public static Money<C> operator +(
Money<C> lhs, Money<C> rhs) {
return new Money<C>() {
Quantity = lhs.Quantity + rhs.Quantity,
Currency = lhs.Currency };
}
}
...
var pizza = new Money<USD>() {
Quantity = 4.99f, Currency = new USD() };
var beer = new Money<EUR>() {
Quantity = 3.5f, Currency = new EUR() };
var lunch = pizza + beer; // ERROR
請注意 USD 及 EUR 基本上只是版面配置區,為了讓編譯器来比較的項目。 如果兩個 c 型別參數不相同,則問題。
當然,我們也就失去了能夠結合這兩個,且會的有時候我們想要這樣。 執行此步驟需要較多個參數的語法 (請參閱圖 2)。
圖 2刻意結合類型
class USD { }
class EUR { }
class Money<C> {
public float Quantity { get; set; }
public C Currency { get; set; }
public static Money<C> operator +(
Money<C> lhs, Money<C> rhs) {
return new Money<C>() {
Quantity = lhs.Quantity + rhs.Quantity,
Currency = lhs.Currency };
}
public Money<C2> Convert<C2>() where C2 : new() {
return new Money<C2>() { Quantity =
this.Quantity, Currency = new C2() };
}
}
這是特殊的泛型方法中泛型類別和 < > 語法之後的方法名稱將多個型別參數加入至方法的範圍 — 在此情況下,第二個貨幣] 轉換為透過轉換成的型別。 因此,現在購買披薩和啤酒變得像:
var pizza = new Money<USD>() {
Quantity = 4.99f, Currency = new USD() };
var beer = new Money<EUR>() {
Quantity = 3.5f, Currency = new EUR() };
var lunch = pizza + beer.Convert<USD>();
如果令人滿意,我們甚至還可以使用轉換運算子 (在 C# 中),自動執行轉換,但有可能有更令人困惑,比很有幫助讀者面前呈現的程式碼,視您提昇盾牌的喜好設定。
完成
什麼是不見金錢 < > 範例非常簡單:很明顯需要有辦法將為歐元美元與歐元轉換成美金。但組件的設計就像這樣的目標是為了避免關閉之系統 — 也就是新的貨幣視 (rubles、 rupees、 磅、 里拉或其他任何浮動貨幣船),最好如果我們,原始的設計工具的金錢 < > 輸入,不需要呼叫可將其加入。理想情況下,在開啟的系統中,其他開發人員可以插入他們所需以及所有項目 「 只是 works。 」
還不要調整,則來說,當然不啟動運送時的程式碼。我們還是必須對金錢 < > 進行一些調整 若要使其更強大、 安全且可延伸的型別。過程中,我們得看動感,也功能性程式設計。
現在,祝各位寫程式!
Ted Neward 是 Neward 與主體 & 建立關聯,專精於企業獨立公司。Net 和 Java 平台系統。他已寫入超過 100 個發行項、 是 C# MVP 和 INETA 演講者和曾著作或與他人合著過十幾書籍,包括"專業 F # 2.0"(Wrox,2010年)。他查閱並定期 mentors,到達他在ted@tedneward.com 如果您想要讓他贏得使用小組,或讀取他的部落格,在 blogs.tedneward.com。
感謝至下列技術專家檢閱這份文件:慣用語法