對元組的語言支援

基本

一個元組是元素的有序,有限的名單。元組通常在程式設計中用作與單個實體一起工作的手段,而不是單獨使用每個元組的元素,並在關聯式資料庫中表示各個行(即記錄)。

在 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;
}

雖然上面示例元組的命名非常通用,但相關標籤的概念允許更深入地理解程式碼中引用 item1item2item3 的內容。

ValueTuple 和 Tuple 之間的差異

引入 ValueTuple 的主要原因是效能。

輸入名稱 ValueTuple Tuple
類或結構 struct class
可變性(建立後改變值) 易變的 一成不變
命名成員和其他語言支援 不( TBD

參考