對元組的語言支援
基本
一個元組是元素的有序,有限的名單。元組通常在程式設計中用作與單個實體一起工作的手段,而不是單獨使用每個元組的元素,並在關聯式資料庫中表示各個行(即記錄)。
在 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 ) |