.NET Magazine國際中文電子雜誌
作 者:許薰尹
審 稿:張智凱
文章編號:N160517101
出刊日期:2016/5/4
開發工具:Visual Studio 2015 Enterprise Update 1
版本:.NET Framework 4.6、C# 6
.NET Framework 4版新增Tuple物件,Tuple是一個有序的資料結構,可以儲存異質物件。在這篇文章之中,將介紹Tuple物件的基本用法。
根據定義,Tuple類别總共有8個多載的Create方法,其定義表列如下:
namespace System {
public static class Tuple {
public static Tuple<T1> Create<T1>( T1 item1 );
public static Tuple<T1 , T2> Create<T1, T2>( T1 item1 , T2 item2 );
public static Tuple<T1 , T2 , T3> Create<T1, T2, T3>( T1 item1 , T2 item2 , T3 item3 );
public static Tuple<T1 , T2 , T3 , T4> Create<T1, T2, T3, T4>( T1 item1 , T2 item2 , T3 item3 , T4 item4 );
public static Tuple<T1 , T2 , T3 , T4 , T5> Create<T1, T2, T3, T4, T5>( T1 item1 , T2 item2 , T3 item3 , T4 item4 , T5 item5 );
public static Tuple<T1 , T2 , T3 , T4 , T5 , T6> Create<T1, T2, T3, T4, T5, T6>( T1 item1 , T2 item2 , T3 item3 , T4 item4 , T5 item5 , T6 item6 );
public static Tuple<T1 , T2 , T3 , T4 , T5 , T6 , T7> Create<T1, T2, T3, T4, T5, T6, T7>( T1 item1 , T2 item2 , T3 item3 , T4 item4 , T5 item5 , T6 item6 , T7 item7 );
public static Tuple<T1 , T2 , T3 , T4 , T5 , T6 , T7 , Tuple<T8>> Create<T1, T2, T3, T4, T5, T6, T7, T8>( T1 item1 , T2 item2 , T3 item3 , T4 item4 , T5 item5 , T6 item6 , T7 item7 , T8 item8 );
}
}
這些多載的Create方法,可以讓你選擇儲存一到七個項目,Create<T1>,表示可以儲存一個項目;Create<T1, T2>方法則可以儲存兩個項目;若要儲存超過8個以上的項目,則可以使用第八個多載方法,利用第八個引數,來建立巢狀式的Tuple物件。接下來讓我們來看看Tuple的應用。
使用Tuple讓方法回傳多個值
C#的方法(Method)中只能夠回傳一個運算過的值,若要回傳多個值,可以將多個值放在陣列或集合一次回傳,或者使用out參數來解決這個問題。但這些做法都有些缺點,使用陣列或集合一次回傳多個值會額外花費一些搜尋其中項目的時間;使用out參數需要額外宣告、初始化out變數。且以上的做法都無法確保執行緒安全(Thread-Safe)。現在Tuple是另一種讓方法回傳多個值的選擇,並且具備執行緒安全的特性。
我們來看看Tuple的應用,例如以下範例程式碼,在GetData方法之中,利用Tuple類別的Create方法,建立可以儲存兩個數值項目的Tuple物件,並將之傳回:
namespace TupleDemo {
class Program {
static void Main( string [ ] args ) {
Tuple<int , int> result = GetData( );
Console.WriteLine( $"SUM = {result.Item1}" );
Console.WriteLine( $"Count = {result.Item2}" );
}
static Tuple<int , int> GetData( ) {
int [ ] data = { 1 , 8 , 52 , 45 , 12 , 44 , 75 , 68 , 24 , 59 };
return Tuple.Create( data.Sum( ) , data.Count( ) );
}
}
}
上述的GetData()方法計算data陣列中數值的總合,以及個數,並利用Tuple將這兩個計算過的數值利用Tuple回傳。在Main方法中我們可以利用屬性的語法(使用「.」符號),來存取Tuple中的項目。若要存取Tuple中的第一個項目,可以使用Item1屬性;若存取Tuple中的第二個項目,可以使用Item2屬性,依此類推。此範例程式的執行結果,請參考下圖所示:

圖 1:使用Tuple儲存兩個數值範例執行結果。
使用new關鍵字建立Tuple實體
除了使用Tuple類別的Create靜態方法來建立Tuple物件之外,你也可以直接使用new關鍵字,來建立Tuple物件的實體,並利用建構函式初始化它的內容,例如我們可以將上述範例程式碼可以改寫如下,並可得到相同的執行結果:
namespace TupleDemo {
class Program {
static void Main( string [ ] args ) {
Tuple<int , int> result = GetData( );
Console.WriteLine( $"SUM = {result.Item1}" );
Console.WriteLine( $"Count = {result.Item2}" );
}
static Tuple<int , int> GetData( ) {
int [ ] data = { 1 , 8 , 52 , 45 , 12 , 44 , 75 , 68 , 24 , 59 };
return new Tuple<int , int>( data.Sum( ) , data.Count( ) );
}
}
}
儲存八個以上的項目
若要在Tuple中儲存八個以上的項目,可以建立巢狀式的Tuple結構,例如以下範例程式碼所示,GetData方法中建立並回傳一個Tuple<int , int , int , int , int , int , int , Tuple<int>>物件,建構函式的第一到七個引數是int型別,第八個引數則是一個Tuple<int>物件。在Main方法中,我們利用Tuple的Rest屬性來取得最後一個Tuple<int>>物件:
namespace TupleDemo {
class Program {
static void Main( string [ ] args ) {
Tuple<int , int , int , int , int , int , int , Tuple<int>> result = GetData( );
Console.WriteLine( $"Item1 = {result.Item1}" );
Console.WriteLine( $"Item2 = {result.Item2}" );
Console.WriteLine( $"Item3 = {result.Item3}" );
Console.WriteLine( $"Item4 = {result.Item4}" );
Console.WriteLine( $"Item5 = {result.Item5}" );
Console.WriteLine( $"Item6 = {result.Item6}" );
Console.WriteLine( $"Item7 = {result.Item7}" );
Console.WriteLine( $"Item8 = {result.Rest.Item1}" );
}
static Tuple<int , int , int , int , int , int , int , Tuple<int>> GetData( ) {
return new Tuple<int , int , int , int , int , int , int , Tuple<int>>( 1 , 2 , 3 , 4 , 5 , 6 , 7 , new Tuple<int>( 8 ) );
}
}
}
此範例程式的執行結果,請參考下圖所示:

圖 2:使用Tuple儲存八個以上的項目。
以下是使用Static方法來儲存八個以上項目的範例:
namespace TupleDemo {
class Program {
static void Main( string [ ] args ) {
Tuple<int , int , int , int , int , int , int , Tuple<int>> result = GetData( );
Console.WriteLine( $"Item1 = {result.Item1}" );
Console.WriteLine( $"Item2 = {result.Item2}" );
Console.WriteLine( $"Item3 = {result.Item3}" );
Console.WriteLine( $"Item4 = {result.Item4}" );
Console.WriteLine( $"Item5 = {result.Item5}" );
Console.WriteLine( $"Item6 = {result.Item6}" );
Console.WriteLine( $"Item7 = {result.Item7}" );
Console.WriteLine( $"Item8 = {result.Rest.Item1}" );
}
static Tuple<int , int , int , int , int , int , int , Tuple<int>> GetData( ) {
return Tuple.Create( 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 );
}
}
}
使用Tuple物件當方法的參數傳遞
Tuple物件可以當做方法的參數來傳遞,這樣的好處是:你可以只宣告一個輸入參數,但可以傳遞不同個數與型別的資料到方法之中。這個設計比C# 語法的可變數目引數(params)來的方便,因為params參數限定只能使用相同的型別,而Tuple物件可以存放不同型別的資料。例如以下範例程式碼,建立一個可以存放兩個項目的Tuple物件,其中的第一個項目是數直型別;第二個項目則是字串型別:
namespace TupleDemo {
class Program {
static void Main( string [ ] args ) {
int [ ] data = { 1 , 8 , 52 , 45 , 12 , 44 , 75 , 68 , 24 , 59 };
string s = $"Count = {data.Count( )}";
Tuple<int , string> parm = Tuple.Create( data.Sum( ) , s );
Print( parm );
}
static void Print( Tuple<int , string> p ) {
Console.WriteLine( $"SUM = {p.Item1}" );
Console.WriteLine( p.Item2 );
}
}
}
在Main方法中,我們將一個陣列中數值資料的總合,與一個字串打包在Tuple之中,並將之傳遞到Print方法之中。使用Tuple讓程式看起來更為簡潔。此範例程式的執行結果,請參考下圖所示:

圖 3:在Tuple物件儲存不同型別的資料。
再者,使用Tuple的好處是,它減少許多使用物件型別的Boxing與Unboxing的程序,使用強型別的方式來儲存資料,因此在開發工具之中,Visual Studio 便會自動提示Tuple其中項目的型別,例如上例的Item1會是數值型別(int),當你在Visual Studio 開發工具中,將滑鼠游標移動到變數上方,馬上就會顯示Tuple內項目的型別資訊;

圖 4:Visual Studio 開發工具自動辨識Tuple項目型別。
而Item2則是字串(string)型別:

圖 5:Visual Studio 開發工具自動辨識Tuple項目型別。
Tuple物件可以儲存特殊結構
我們可以在Tuple物件中儲存集合或陣列物件,來設計更特殊的資料結構,例如你同時想要儲存學生的姓名、年齡與興趣,興趣包含多種項目,你可以宣告如下的Tuple物件,利用List<string>來描述學生的興去有多個:
namespace TupleDemo {
class Program {
static void Main( string [ ] args ) {
var student = Tuple.Create(
"Mary" ,
28 ,
new List<string>( ) { "walking" , "swimming" }
);
Console.WriteLine( student.Item1 );
Console.WriteLine( student.Item2 );
Console.WriteLine( "Interest :" );
student.Item3.ForEach( s => Console.WriteLine( $"\t{s}" ) );
}
}
}
此範例程式的執行結果,請參考下圖所示:

圖 6:Tuple物件可以儲存特殊結構。
當然,你也可以自行設計一個類別來做到相同的事情,不過Tuple的好處是:它具備執行緒安全(Thread-Safe)的特性,你不需要煩惱這個問題。
在集合中儲存Tuple物件
我們也可以在集合之中儲存多個Tuple物件,例如以下範例程式碼,建立一個students集合,儲存Tuple<string , int , List<string>>型別的項目。利用集合的Add方法,加入多個Tuple物件到集合之中,最後使用foreach迴圈,將其中的項目的值印出:
namespace TupleDemo {
class Program {
static void Main( string [ ] args ) {
List<Tuple<string , int , List<string>>> students = new List<Tuple<string , int , List<string>>>( );
students.Add( Tuple.Create(
"Mary" ,
28 ,
new List<string>( ) { "walking" , "swimming" }
) );
students.Add( Tuple.Create(
"Candy" ,
35 ,
new List<string>( ) { "Playing video game" , "Climbing" }
) );
foreach ( var item in students ) {
Console.WriteLine( item.Item1 );
Console.WriteLine( item.Item2 );
Console.WriteLine( "Interest :" );
item.Item3.ForEach( s => Console.WriteLine( $"\t{s}" ) );
Console.WriteLine( "===============================" );
}
}
}
}
此範例程式的執行結果,請參考下圖所示:

圖 7:在集合中儲存Tuple物件。
建立Tuple陣列
宣告Tuple陣列和宣告Tuple集合沒有什麼太大的不同,參考以下範例程式碼,建立三個Tuple<string>物件,儲存在陣列之中:
namespace TupleDemo {
class Program {
static void Main( string [ ] args ) {
Tuple<string> [ ] data = new Tuple<string> [ ] {
Tuple.Create("Mary"),Tuple.Create("Candy"),Tuple.Create("Judy")
};
foreach ( Tuple<string> item in data ) {
Console.WriteLine( item.Item1 );
}
}
}
}
此範例程式的執行結果,請參考下圖所示:

圖 8:建立Tuple陣列。
排序Tuple項目
最後我們談談排序的問題,若要對集合或陣列中的項目進行排序,可以利用OrderBy(由小到大)或OrderByDescending(由大到小)擴充方法。以下範例程式碼,叫用OrderBy方法,根據學生的名稱排序,由小排到大,再利用for each 迴圈將其中的學生資料一一印出:
namespace TupleDemo {
class Program {
static void Main( string [ ] args ) {
List<Tuple<string , int , List<string>>> students = new List<Tuple<string , int , List<string>>>( );
students.Add( Tuple.Create(
"Mary" ,
28 ,
new List<string>( ) { "walking" , "swimming" }
) );
students.Add( Tuple.Create(
"Candy" ,
35 ,
new List<string>( ) { "Playing video game" , "Climbing" }
) );
foreach ( var item in students.OrderBy( t => t.Item1 ) ) {
Console.WriteLine( item.Item1 );
Console.WriteLine( item.Item2 );
Console.WriteLine( "Interest :" );
item.Item3.ForEach( s => Console.WriteLine( $"\t{s}" ) );
Console.WriteLine( "===============================" );
}
}
}
}
此範例程式的執行結果,請參考下圖所示:

圖 9:排序Tuple項目。