對元組的語言支援
基本
一個元組是元素的有序,有限的名單。元組通常在程式設計中用作與單個實體一起工作的手段,而不是單獨使用每個元組的元素,並在關聯式資料庫中表示各個行(即記錄)。
在 C#7.0 中,方法可以有多個返回值。在幕後,編譯器將使用新的 ValueTuple 結構。
public (int sum, int count) GetTallies() 
{
    return (1, 2);
}
附註 :為了在 Visual Studio 2017 中工作,你需要獲取 System.ValueTuple 包。
如果將元組返回方法結果分配給單個變數,則可以通過方法簽名上的已定義名稱訪問成員:
var result = GetTallies();
// > result.sum
// 1
// > result.count
// 2
元組解構
元組解構將元組分成了它的部分。
例如,呼叫 GetTallies 並將返回值賦值給兩個單獨的變數會將元組解構為這兩個變數:
(int tallyOne, int tallyTwo) = GetTallies();
var 也有效:
(var s, var c) = GetTallies();
你也可以使用更短的語法,var 以外的 var:
var (s, c) = GetTallies();
你還可以解構為現有變數:
int s, c;
(s, c) = GetTallies();
交換現在變得更加簡單(不需要臨時變數):
(b, a) = (a, b);
有趣的是,任何物件都可以通過在類中定義 Deconstruct 方法來解構:
class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public void Deconstruct(out string firstName, out string lastName)
    {
        firstName = FirstName;
        lastName = LastName;
    }
}
var person = new Person { FirstName = "John", LastName = "Smith" };
var (localFirstName, localLastName) = person;
在這種情況下,(localFirstName, localLastName) = person 語法在 person 上呼叫 Deconstruct。
甚至可以用擴充套件方法來定義解構。這相當於上面的內容:
public static class PersonExtensions
{
    public static void Deconstruct(this Person person, out string firstName, out string lastName)
    {
        firstName = person.FirstName;
        lastName = person.LastName;
    }
}
var (localFirstName, localLastName) = person;
Person 類的另一種方法是將 Name 本身定義為 Tuple。考慮以下:
class Person
{
    public (string First, string Last) Name { get; }
    public Person((string FirstName, string LastName) name)
    {
        Name = name;
    }
}
然後你可以像這樣例項化一個人(我們可以把一個元組作為引數):
var person = new Person(("Jane", "Smith"));
var firstName = person.Name.First; // "Jane"
var lastName = person.Name.Last;   // "Smith"
元組初始化
你還可以在程式碼中任意建立元組:
var name = ("John", "Smith");
Console.WriteLine(name.Item1);
// Outputs John
Console.WriteLine(name.Item2);
// Outputs Smith
建立元組時,你可以為元組的成員分配特殊專案名稱:
var name = (first: "John", middle: "Q", last: "Smith");
Console.WriteLine(name.first);
// Outputs John
型別推斷
使用相同簽名(匹配型別和計數)定義的多個元組將被推斷為匹配型別。例如:
public (int sum, double average) Measure(List<int> items)
{
    var stats = (sum: 0, average: 0d);
    stats.sum = items.Sum();
    stats.average = items.Average();
    return stats;
}
stats 可以返回,因為 stats 變數的宣告和方法的返回簽名是匹配的。
反射和元組欄位名稱
成員名稱在執行時不存在。即使成員名稱不匹配,Reflection 也會考慮具有相同數量和型別的成員的元組。將元組轉換為 object 然後轉換為具有相同成員型別但名稱不同的元組也不會導致異常。
雖然 ValueTuple 類本身不保留成員名稱的資訊,但可以通過 TupleElementNamesAttribute 中的反射獲得資訊。此屬性不應用於元組本身,而是應用於方法引數,返回值,屬性和欄位。這使得元組專案名稱被保留整個元件,即如果一個方法返回(字串名稱,詮釋計數)名稱名稱和數量將可在另一組裝方法的呼叫者,因為返回值將包含值 TupleElementNameAttribute 標記名字和計數。
與泛型和 async 一起使用
新的元組功能(使用底層的 ValueTuple 型別)完全支援泛型,可以用作泛型型別引數。這使得它可以與 async / await 模式一起使用:
public async Task<(string value, int count)> GetValueAsync()
{
    string fooBar = await _stackoverflow.GetStringAsync();
    int num = await _stackoverflow.GetIntAsync();
    return (fooBar, num);
}
與集合一起使用
在一個場景中有一個元組集合可能會變得有益,在這個場景中,你試圖根據條件找到匹配的元組以避免程式碼分支。
例:
private readonly List<Tuple<string, string, string>> labels = new List<Tuple<string, string, string>>()
{
    new Tuple<string, string, string>("test1", "test2", "Value"),
    new Tuple<string, string, string>("test1", "test1", "Value2"),
    new Tuple<string, string, string>("test2", "test2", "Value3"),
};
public string FindMatchingValue(string firstElement, string secondElement)
{
    var result = labels
        .Where(w => w.Item1 == firstElement && w.Item2 == secondElement)
        .FirstOrDefault();
    if (result == null)
        throw new ArgumentException("combo not found");
    return result.Item3;
}
隨著新元組可以成為:
private readonly List<(string firstThingy, string secondThingyLabel, string foundValue)> labels = new List<(string firstThingy, string secondThingyLabel, string foundValue)>()
{
    ("test1", "test2", "Value"),
    ("test1", "test1", "Value2"),
    ("test2", "test2", "Value3"),
}
public string FindMatchingValue(string firstElement, string secondElement)
{
    var result = labels
        .Where(w => w.firstThingy == firstElement && w.secondThingyLabel == secondElement)
        .FirstOrDefault();
    if (result == null)
        throw new ArgumentException("combo not found");
    return result.foundValue;
}
雖然上面示例元組的命名非常通用,但相關標籤的概念允許更深入地理解程式碼中引用 item1,item2 和 item3 的內容。
ValueTuple 和 Tuple 之間的差異
引入 ValueTuple 的主要原因是效能。
| 輸入名稱 | ValueTuple | 
Tuple | 
|---|---|---|
| 類或結構 | struct | 
class | 
| 可變性(建立後改變值) | 易變的 | 一成不變 | 
| 命名成員和其他語言支援 | 是 | 不( TBD ) |