共用方式為



2017 年 8 月

第 32 卷,第 8 期

本文章是由機器翻譯。

基礎 .NET - C# 7.0:關於 Tuple

標記 Michaelis

作者:Mark Michaelis在 [connect (); 在 11 月版特殊的問題,我會提供的 C# 7.0 概觀 (msdn.microsoft.com/magazine/mt790178),我導入的 tuple。在本文中,我深入 tuple 同樣地,涵蓋完整範圍的語法選項。

若要開始,讓我們考慮這個問題:為什麼 tuple 嗎? 在某些情況下,您就可能會有用來結合資料元素。例如,假設您正在使用的國家/地區,例如在 2017年世界中的最差國家/地區的資訊:馬拉威,其資本受到 Lilongwe,與總國內產品 (GDP) 每個資本的 $226.50。很明顯地,您可以宣告的類別,此資料,但它實際上並不代表一般名詞/物件。它是表面上多個集合的相關資料比物件。當然,如果您要有一個國家 (地區) 的物件,例如,就會相當多的資料比只是屬性名稱、 大寫及 GDP 每 capita。或者,您可以在個別的變數中,儲存每個資料元素,但結果會是資料元素之間沒有關聯$226.50 中變數的名稱必須與馬拉威以外沒有關聯,或許是由一般的後置字元或前置詞。另一個選項,可將所有的資料結合成單一字串 — 壞處個別使用每個資料元素,可能需要進行剖析時。最後的方法可能是建立匿名型別,但是,也有限制。足夠,事實上,,tuple 可以潛在匿名型別完全取代。我會將本主題的結尾發行項的保留狀態。

最佳選項可能就是 C# 7.0 tuple,其中在最簡單的提供可讓您結合多個變數,在單一陳述式中的不同類型的指派的語法:

(string country, string capital, double gdpPerCapita) = 
  ("Malawi", "Lilongwe", 226.50);

在此情況下,我是不只指派多個變數,但也宣告它們。

不過,tuple 都具有其他幾個其他語法可能性,每個所示圖 1

圖 1-Tuple 宣告和指派的範例程式碼

範例描述範例程式碼
1.指派至個別宣告變數的 tuple。(字串國家 (地區)、 字串資本、 double gdpPerCapita) =
(「 馬拉威"、"Lilongwe",226.50);
System.Console.WriteLine (
$@"The 在 2017年世界中的最差國家/地區的 {
國家/地區},{首都}: {gdpPerCapita}");
2.指派 tuple 預先宣告的個別宣告變數。字串國家 (地區)。
字串的大寫;
double gdpPerCapita;
(國家/地區、 大寫、 gdpPerCapita) =
(「 馬拉威"、"Lilongwe",226.50);
System.Console.WriteLine (
$@"The 在 2017年世界中的最差國家/地區的 {
國家/地區},{首都}: {gdpPerCapita}");
3.個別指派至 tuple 宣告和隱含類型變數。(var 國家/地區,var 資本 var gdpPerCapita) =
(「 馬拉威"、"Lilongwe",226.50);
System.Console.WriteLine (
$@"The 在 2017年世界中的最差國家/地區的 {
國家/地區},{首都}: {gdpPerCapita}");
4.若要進行隱含型別與分散式語法的個別宣告變數指派 tuple。var (國家/地區、 大寫、 gdpPerCapita) =
(「 馬拉威"、"Lilongwe",226.50);
System.Console.WriteLine (
$@"The 在 2017年世界中的最差國家/地區的 {
國家/地區},{首都}: {gdpPerCapita}");
5.宣告具名項目 tuple 和 tuple 值指派給它,然後依名稱存取的 tuple 項目。(字串名稱,字串資本、 double GdpPerCapita) countryInfo =
(「 馬拉威"、"Lilongwe",226.50);
System.Console.WriteLine (
$@"The 在 2017年世界中的最差國家/地區的 {
countryInfo.Name},{countryInfo.Capital}: {
countryInfo.GdpPerCapita}");
6.將具名項目 tuple 指派給隱含類型的單一隱含類型變數,然後依名稱存取的 tuple 項目。var countryInfo =
(名稱:「 馬拉威",大寫字母:「 Lilongwe",GdpPerCapita:226.50);
System.Console.WriteLine (
$@"The 在 2017年世界中的最差國家/地區的 {
countryInfo.Name},{countryInfo.Capital}: {
countryInfo.GdpPerCapita}");
7.將未命名的 tuple 指派給單一的隱含類型變數,然後存取其項目編號屬性 tuple 項目。var countryInfo =
(「 馬拉威"、"Lilongwe",226.50);
System.Console.WriteLine (
$@"The 在 2017年世界中的最差國家/地區的 {
countryInfo.Item1},{countryInfo.Item2}: {
countryInfo.Item3}");
8.將具名項目 tuple 指派給單一的隱含類型變數,然後存取其項目編號屬性 tuple 項目。var countryInfo =
(名稱:「 馬拉威",大寫字母:「 Lilongwe",GdpPerCapita:226.50);
System.Console.WriteLine (
$@"The 在 2017年世界中的最差國家/地區的 {
countryInfo.Item1},{countryInfo.Item2}: {
countryInfo.Item3}");
9.捨棄 tuple 與底線的部分。(字串名稱、 _、 double gdpPerCapita) countryInfo =
(「 馬拉威"、"Lilongwe",226.50);

在前四個範例中,而左手邊右手邊代表 tuple,雖然仍代表個別指派一起使用的變數 tuple 語法其中牽涉到兩個或多個項目以逗號分隔並括號與相關聯。(我使用詞彙 tuple 語法,因為編譯器會產生左側的基礎資料類型不是技術上的 tuple。) 結果是我的開頭為右邊 tuple 合併的值,雖然左邊的指派會將 tuple 解構為它的構成部分。在範例 2 中,左右手邊指派為預先宣告的變數。不過,在範例 1、 3 和 4 中,變數宣告中的 tuple 語法。命名和大小寫慣例假設我只宣告變數,後面通常可接受的 Framework 設計方針 — [請勿使用 camelCase 本機變數的名稱,"範例。

請注意,雖然隱含的輸入 (var) 可以分散安裝在每個變數宣告中的 tuple 語法,如範例 4 中所示,您無法執行相同動作明確類型 (例如字串)。實際宣告元組型別,不只使用 tuple 語法在此案例中,因此,您必須將參考加入 System.ValueType NuGet 套件 — 至少直到.NET 標準 2.0。Tuple 允許將不同的資料類型的每個項目,因為所有項目之間分散明確的類型名稱不一定運作,除非是相同的所有項目資料類型 (和即使如此,編譯器不允許它)。

在範例 5 我宣告 tuple 的左側,然後再指派右邊的 tuple。請注意 tuple 具有名為項目: 命名您可以再擷取 tuple 從傳回的項目值的參考。這可讓 countryInfo.Name、 countryInfo.Capital 和 countryInfo.GdpPerCapita System.Console.WriteLine 陳述式中的語法。Tuple 宣告在左邊的結果是一個變數分組到單一變數 (countryInfo) 您接著可以從中存取構成部分。這是很有用,因為您接著可以將周圍這個單一變數傳遞給其他方法,而且這些方法也會無法存取之 tuple 中的個別項目。

如先前所述,使用 tuple 語法使用 camelCase 定義的變數。不過,tuple 項目名稱的慣例無法妥善定義。建議包含 tuple 的行為類似參數如何使用參數命名慣例,例如當傳回多個值的 tuple 之前語法會使用 out 參數。替代方案是使用 PascalCase,遵循公用欄位和屬性的命名慣例。我強烈偏向第二種方法,根據大小寫規則,識別項 (itl.tc/caprfi)。Tuple 項目名稱會轉譯為 tuple 的成員,而且所有 (公用) 的成員 (這可能存取使用點運算子) 的慣例形式為 PascalCase。

範例 6 提供與範例 5 相同的功能,雖然會使用具名的 tuple 項目上的右手邊 tuple 值並在左邊的隱含型別宣告。項目名稱會保存給隱含類型變數,不過,因此它們仍可供 WriteLine 陳述式。當然,這會開啟您可以將具有不同的使用右側的名稱命名的項目左側的可能性。雖然 C# 編譯器允許您為此,它會發出警告,右邊的項目名稱將會忽略左的優先上的一樣。

如果不指定了任何項目名稱,個別的項目仍指派的 tuple 變數中。不過,名稱是 Item1、 Item2,依此類推,7 範例所示。事實上,ItemX 名稱永遠是用於 tuple,即使當自訂名稱提供 (請參閱範例 8)。不過,當使用 IDE 工具的 Visual Studio 支援 C# 7.0 的新類別一樣,ItemX 屬性不會出現 [IntelliSense] 下拉式清單中,好了因為大概所提供的名稱會是偏好。

範例 9 所示,可以使用底線; 排除部分 tuple 指派這稱為捨棄。

Tuple 是封裝成單一物件的資料一樣包可能會擷取您從存放區挑選的其他項目中的輕量級解決方案。與陣列不同的是 tuple 包含有所不同幾乎沒有條件約束 (雖然不允許指標) 的項目資料類型,不同之處在於它們在識別出程式碼,而且無法變更執行階段。此外,不同於使用陣列時,tuple 內的項目數是硬式編碼在編譯時期,以及。最後,您無法將自訂行為加入至 tuple (儘管的擴充方法)。如果您需要封裝的資料相關聯的行為,然後利用物件導向程式設計與定義的類別是慣用的方法。

System.ValueTuple <>...類型

C# 編譯器會依賴泛型值類型 (結構),例如 System.ValueTuple < T1、 T2、 T3 > 一組程式碼產生做為基礎的實作,tuple 的所有執行個體右手邊的範例中的 tuple 語法圖 1。同樣地,同一組 System.ValueTuple <>...泛型值類型用於與範例 5 開始的左右手邊的資料類型。如您所預期的元組型別,包含的唯一方法是相關比較和等號比較。不過,可能是意外,有沒有 ItemX,但而不是讀寫欄位的屬性 (看似中斷最基本的.NET 程式設計指導方針所述在itl.tc/CS7TuplesBreaksGuidelines)。

除了程式設計的指導方針不一致,有為另一個,就會發生的行為問題。假設的自訂項目名稱和其類型不包含 System.ValueTuple <>...定義中,如何它可能是每個自訂項目名稱是表面 System.ValueTuple <>...類型且為該類型的成員可存取成員?

什麼是令人意外 (特別是針對其熟悉的匿名型別實作) 是編譯器無法產生基礎的通用中繼語言 (CIL) 程式碼,對應於自訂名稱的成員。不過,即使沒有的自訂名稱為基礎的成員,沒有 (看似) 從 C# 觀點來看,這類成員。

所有具名的 tuple 本機變數範例中,例如:

var countryInfo = (Name: "Malawi", Capital: "Lilongwe", GdpPerCapita: 226.50)

很清楚地可能 tuple 的範圍內的其餘部分,編譯器無法已知的名稱,因為該範圍限定在宣告它的成員。而且,事實上,編譯器 (和 IDE) 很簡單依賴此領域可讓您依名稱存取每個項目。換句話說,編譯器會查看 tuple 宣告內的項目名稱,並運用它們允許使用這些名稱範圍內的程式碼。它是基於這個理由,以及,ItemX 方法不會在 IDE IntelliSense 中顯示為可用 tuple 成員 (IDE 只會略過它們並將它們取代為具名的項目)。

判斷項目名稱,從範圍內的成員執行時是合理的編譯器,但 tuple 公開外部成員時,會發生什麼事,例如參數或傳回不同的組件 (其有可用可能沒有原始程式碼) 中的方法嗎? 所有 tuple 的應用程式開發介面的組件 (不論公用或私用應用程式開發介面),編譯器會將項目名稱加入至屬性形式中成員的中繼資料。例如,下列項目:

[return: System.Runtime.CompilerServices.TupleElementNames(
  new string[] {"First", "Second"})]
public System.ValueTuple<string, string> ParseNames(string fullName)
{
  // ...
}

相當於 C# 的編譯器會產生下列:

public (string First, string Second) ParseNames(string fullName)

相關的提示,C# 7.0 無法使用明確的 System.ValueTuple <> … 資料型別時讓自訂項目名稱的用法。因此,如果您取代中的範例 8 var圖 1,您會得到警告將被忽略的每個項目名稱。

以下是幾個額外的其他事實記住關於 System.ValueTuple <> …:

  • 有八個對應,可能會支援最多七項目與 tuple 的泛型 System.ValueTuple 結構的總計。第八個 tuple,System.ValueTuple < T1、 T2、 T3、 T4、 T5、 T6、 T7、 TRest >,最後一個型別參數可讓您指定其他值 tuple,進而支援 n 個項目。如果比方說,您會使用 8 參數指定 tuple,編譯器會自動產生 System.ValueTuple < T1、 T2、 T3、 T4、 T5 T6、 T7,System.ValueTuple < TSub1 >> 做為基礎的實作類型。(為求完整起見,System.Value < T1 > 存在,但將真的只能直接使用,只為型別。它不會使用直接由編譯器因為 C# tuple 語法需要至少兩個項目。)
  • 沒有非泛型 System.ValueTuple 做為 tuple 處理站,以對應至每個數值 tuple 引數數目的建立方法。使用常值,例如 var t1 tuple 的輕鬆 = (「 Inigo Montoya",42)、 C# 7.0 (含) 以後程式設計人員至少取代 Create 方法。
  • 實際上,C# 開發人員可以基本上忽略 System.ValueTuple 和 System.ValueTuple < T >。

沒有其他隨附於.NET Framework 4.5 的元組型別 — System.Tuple <>...。在這段時間,它必須是接下來的核心 tuple 實作。但是,一旦 C# 支援 tuple 語法,它已知道,實值類型通常會執行更好,所以 System.ValueTuple <>...引進,有效地取代 System.Tuple <>...在所有情況下,回溯相容性的現有應用程式開發介面,取決於 System.Tuple <>...除外。

總結

首次推出時,許多人什麼不知道是新的 C# 7.0 tuple 以外的所有取代匿名型別,並提供額外的功能。可從方法傳回 Tuple,例如,項目名稱會保存在 API 中,有意義的名稱可以用來取代 ItemX 類型命名。此外,例如匿名型別,tuple 甚至可以代表複雜的階層式結構,例如可能更複雜的 LINQ 查詢中建構 (儘管像具有匿名類型,開發人員應該執行此作業時多加注意)。話雖如此,這可能會導致以及的情況下 tuple 實值型別超出 128 個位元組,因此,可能會使用匿名型別,因為它是參考類型的時機的角案例。除了這些極端案例 (透過一般反映來存取可能是另一個範例),只有少許以沒有必要程式設計 [C# 7.0 或更新版本時,使用匿名型別。

程式與 tuple 類型物件的能力已久,很長的時間 (如所述,tuple 類別,System.Tuple <>...,是否引進.NET Framework 4,但可在 Silverlight 中之前會先取得)。不過,這些解決方案從未安裝過隨附的 C# 語法,但而不是 nothing 超過.NET API。C# 7.0 使第一級的 tuple 語法可讓常值 — 例如 var tuple = (42"Inigo Montoya 」) — 隱含型別,強式類型公用應用程式開發介面的使用率,整合 IDE 支援具名 ItemX 資料等等。不可否認,可能無法將您在每個 C# 檔案中使用的項目,但很可能透過 out 參數或匿名型別替代方案,您應該能夠在需要時很感謝你的某項目而且您將歡迎使用 tuple 語法。

這篇文章的許多衍生自我"基本 C#"的活頁簿 (IntelliTect.com/EssentialCSharp),其中我目前之中更新為 「 基本 C# 7.0。 」 如需本主題的詳細資訊,請參閱第 3 章。

TUPLE 項目命名指導方針

請勿camelCase 用於使用 tuple 語法宣告所有變數。

請考慮PascalCase 使用的所有 tuple 項目名稱。


標記 Michaelis是創辦 IntelliTect,他作為其主要技術架構設計人員和訓練。幾乎二十他經過 Microsoft MVP,以及 Microsoft 地區導向器自 2007年。Michaelis 做數個 Microsoft 軟體設計檢閱小組成員,包括 C#、 Microsoft Azure、 SharePoint 和 Visual Studio ALM。他在開發人員所做的心得,而且已經寫許多書籍,包括其最新,「 基本 C# 6.0 (第 5 版) 」 (itl.tc/EssentialCSharp)。在 Facebook 上連絡他facebook.com/Mark.Michaelis,在他的部落格上IntelliTect.com/Mark,Twitter 上: @markmichaelis或透過電子郵件在mark@IntelliTect.com

感謝您檢閱本文章下列的 Microsoft 技術專家:Mads Torgersen


MSDN Magazine 論壇中的這篇文章的討論