元組功能提供了簡潔的語法來將多個數據元素分組成一個輕型數據結構。下面的示例演示瞭如何聲明元組變量、對它進行初始化並訪問其數據成員:
double [ ] SJDs = [ 1.5 , 2.3 , 3.6 ];
( double 和 , int 個數 ) = ( SJDs . Sum ( ) , SJDs . Length );
Console . WriteLine ( $"SJDs 的總和:{和},個數:{個數}" );
如前面的示例所示,若要定義元組類型,需要指定其所有數據成員的類型,或者,可以指定字段名稱(或不指定,分別為 Item1、Item2……)。雖然不能在元組類型中定義方法,但可以使用 .NET 提供的方法,如下面的示例所示:
double [ ] SJDs = [ 1.5 , 2.3 , 3.6 ];
( double 和 , int 個數 ) TJ = ( SJDs . Sum ( ) , SJDs . Length );
Console . WriteLine ( TJ . ToString ( ) );
Console . WriteLine ( $"{TJ} 的哈希代碼:{TJ . GetHashCode ( )}" );
元組類型支持相等運算符 == 和 !=。
元組類型是值類型;元組元素是公共字段。這使得元組為可變的值類型。
可以使用任意數量的元素定義元組:
var Zhss =
( 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 ,
11 , 12 , 13 , 14 , 15 , 16 , 17 , 18 ,
19 , 20 , 21 , 22 , 23 , 24 , 25 , 26 );
Console . WriteLine ( Zhss . Item26 ); // 26
元組的用例
元組最常見的用例之一是作為方法返回類型。也就是説,你可以將方法結果分組為元組返回類型,而不是定義 out 方法參數,如以下示例所示:
( int 小 , int 大 ) FF小和大 ( int [ ] Zhss )
{
if ( Zhss is null || Zhss . Length == 0 )
{
throw new ArgumentException ( "無法在 null 或空數組中查找最大值和最小值。" );
}
// 將“Zhs小”初始化為“MaxValue”,這樣輸入中的每個值都會小於這個初始值;將“Zhs大”初始化為“MinValue”,這樣輸入中的每個值都會大於這個初始值
int Zhs小 = Zhss . Max ( ) , Zhs大 = Zhss . Min ( );
foreach ( int z in Zhss )
{
if ( z < Zhs小 )
{
Zhs小 = z;
}
if ( z > Zhs大 )
{
Zhs大 = z;
}
}
return ( Zhs小 , Zhs大 );
}
int [ ] ZhssX = [ 14 , 77 , 69 ];
var XZ = FF小和大 ( ZhssX );
Console . WriteLine ( $"對於 {string . Join ( " " , ZhssX )} 的限制:小 {XZ . 小} ~ 大 {XZ . 大}" ); // 14 ~ 77
int [ ] ZhssY = [ 24 , 6 , 123 , 258 ];
var ( X , D ) = FF小和大 ( ZhssY );
Console . WriteLine ( $"對於 {string . Join ( " " , ZhssY )} 的限制:小 {X} ~ 大 {D}" ); // 6 ~ 258
如前面的示例所示,可以直接使用返回的元組實例,或者可以在單獨的變量中析構它。
還可以使用元組類型,而不是匿名類型;例如,在 LINQ 查詢中。
通常,你會使用元組對相關的數據元素進行鬆散分組。在公共 API 中,請考慮定義類或結構類型。
元組字段名稱
可以在元組初始化表達式中或元組類型的定義中顯式指定元組字段名稱,如下面的示例所示:
var t = ( 和 : 4.5 , 個數 : 3 );
Console . WriteLine ( $"和:{t . 和},共 {t . 個數} 個元素。" );
( double 和 , int 個數 ) d = ( 4.5 , 3 );
Console . WriteLine ( $"和:{d . 和},共 {d . 個數} 個元素。" );
如果未指定字段名稱,則可以根據元組初始化表達式中相應變量的名稱推斷出此名稱,如下面的示例所示:
var 和 = 4.5;
var 個數 = 3;
var t = ( 和 , 個數 );
Console . WriteLine ( $"{t . count} 個元素的和:{t . sum}。");
這稱為元組投影初始值設定項。在以下情況下,變量名稱不會被投影到元組字段名稱中:
- 候選名稱是元組類型的成員名稱,例如 Item3、ToString 或 Rest。
- 候選名稱是另一元組的顯式或隱式字段名稱的重複項。
在前面的示例中,你可以顯式指定字段的名稱,或按字段的默認名稱訪問字段。
元組字段的默認名稱為 Item1、Item2、Item3 等。始終可以使用字段的默認名稱,即使字段名稱是顯式指定的或推斷出的,如下面的示例所示:
var a = 1;
var t = ( a , b: 2 , 3 );
Console . WriteLine ( $"第一個元素是:{t.Item1} (類似於 {t . a})。" );
Console . WriteLine ( $"第二個元素是:{t.Item2}(類似於 {t . b} )。" );
Console . WriteLine ( $"第三個元素是:{t . Item3}。" );
// 第一個元素是:1(類似於 1)。
// 第二個元素是:2(類似於 2)。
// 第三個元素是:3。
元組賦值和元組相等比較不會考慮字段名稱。
在編譯時,編譯器會將非默認字段名稱替換為相應的默認名稱。因此,顯式指定或推斷的字段名稱在運行時不可用。
提示:啓用 .NET 代碼樣式規則 IDE0037 以設置推理或顯式元組字段名稱的首選項。
從 C# 12 開始,可以使用 using 指令指定元組類型的別名。以下示例為元組類型添加 global using 別名,其中包含表示允許的 Min 值和 Max 值的兩個整數值:
global using DT濾波器 = ( int D值 , int G值 );
聲明別名後,可以將 DT濾波器 名稱用作該元組類型的別名:
DT濾波器 KH = ( 40 , 100 );
Console . WriteLine ( $"帶通濾波器限制:{KH . D值} 到 {KH . G值}" );
別名不會引入新類型,而只會為現有類型創建同義詞。可以析構使用 DT濾波器 別名聲明的元組,就像析構其基礎元組類型一樣:
( int D , int G ) = KH;
Console . WriteLine ( $"KH 是 {D} 到 {G}");
與元組賦值或析構一樣,元組成員名稱不需要進行匹配;類型需要進行匹配。
同樣,具有相同 arity 和成員類型的第二個別名可以與原始別名互換使用。可以聲明第二個別名:
using Range = ( int D , int G );
可以將 Range 元組分配給 BandPass 元組。 與所有元組賦值一樣,字段名稱不需要進行匹配,只有類型和 arity 需要進行匹配。
Range r = KH;
Console . WriteLine ( $"範圍是:{r . D} 到 {r . G}" );
使用元組時,元組類型的別名提供更多語義信息。它不會引入新類型。若要提供類型安全性,應改為聲明位置 record。
元組賦值和析構
C# 支持滿足以下兩個條件的元組類型之間的賦值:
- 兩個元組類型有相同數量的元素
- 對於每個元組位置,右側元組元素的類型與左側相應的元組元素的類型相同或可以隱式轉換為左側相應的元組元素的類型
元組元素是按照元組元素的順序賦值的。元組字段的名稱會被忽略且不會被賦值,如下面的示例所示:
( int , double ) t1 = ( 17 , 3.14 );
( double First , double Second ) t2 = ( 0.0 , 1.0 );
t2 = t1;
Console . WriteLine ( $"{nameof ( t2 )}:{t2 . First} 和 {t2 . Second}" ); // t2:17 和 3.14
( double A , double B ) t3 = ( 2.0 , 3.0 );
t3 = t2;
Console . WriteLine ( $"{nameof ( t3 )}:{t3 . A} 和 {t3 . B}" ); // t3:17 和 3.14
還可以使用賦值運算符 = 在單獨的變量中析構元組實例。可以通過許多方式來進行此操作:
-
在括號外使用 var 關鍵字來聲明隱式類型化變量,並讓編譯器推斷其類型:
var t = ("郵局" , 3.6 ); var ( ShuoMing , JuLi ) = t; Console . WriteLine ( $"距離到 {ShuoMing} 是:{距離} 公里。"); // 距離到 郵局 是: 3.6 公里。 -
在括號內顯示説明每個變量的類型:
var t = ( "郵局" , 3.6 ); ( var ShuoMing , double JuLi ) = t; Console . WriteLine ( $"距離到 {ShuoMing} 是:{JuLi} 公里。" ); // 距離到 郵局 是:3.6 公里。析構表達式的目標可以包括現有變量和析構聲明中聲明的變量。
還可以將析構與模式匹配相結合,以檢查元組中字段的特徵。以下示例循環訪問多個整數,並輸出可被 3 整除的整數。它析構 Int32 . DivRem 的元組結果,並與 Remainder 0 進行匹配:
for ( int z = 4; z < 20; z++ )
{
if ( Math . DivRem ( z , 3 ) is ( 商 : var s , 餘數 : 0 ) )
{
Console . WriteLine ( $"{i} 能被 3 整除,商 {s}" );
}
}
元組相等
元組類型支持 == 和 != 運算符。這些運算符按照元組元素的順序將左側操作數的成員與相應的右側操作數的成員進行比較。
( int a , byte b ) z = ( 5 , 10 );
( long a , int b ) y = ( 5 , 10 );
Console . WriteLine ( z == y ); // True
Console . WriteLine ( z != y ); // False
var t1 = ( A : 5 , B : 10 );
var t2 = ( B : 5 , A : 10 );
Console . WriteLine ( t1 == t2 ); // True
Console . WriteLine ( t1 != t2 ); // False
如前面的示例所示,== 和 != 操作不會考慮元組字段名稱。
同時滿足以下兩個條件時,兩個元組可比較:
- 兩個元組具有相同數量的元素。 例如,如果 t1 和 t2 具有不同數目的元素,t1 != t2 則不會進行編譯。
- 對於每個元組位置,可以使用 == 和 != 運算符對左右側元組操作數中的相應元素進行比較。例如,( 1 , ( 2 , 3 ) ) == ( ( 1 , 2 ) , 3 ) 不會進行編譯,因為 1 不可與 ( 1 , 2 ) 比較。
== 和 != 運算符將以短路方式對元組進行比較。也就是説,一旦遇見一對不相等的元素或達到元組的末尾,操作將立即停止。但是,在進行任何比較之前,將對所有元組元素進行計算,如以下示例所示:
Console . WriteLine ( ( Display ( 1 ) , Display ( 2 ) ) == ( Display ( 3 ) , Display ( 4 ) ) );
int Display ( int s )
{
Console . WriteLine ( s );
return s;
}
// 1
// 2
// 3
// 4
// False
元組作為 out 參數
通常,你會將具有 out 參數的方法重構為返回元組的方法。但是,在某些情況下,out 參數可以是元組類型。下面的示例演示瞭如何將元組作為 out 參數使用:
var limitsLookup = new Dictionary < int , ( int Min , int Max ) > ( )
{
[ 2 ] = ( 4 , 10 ),
[ 4 ] = ( 10 , 20 ),
[ 6 ] = ( 0 , 23 )
};
if ( limitsLookup . TryGetValue ( 4 , out ( int Min , int Max ) limits ) )
{
Console . WriteLine ( $"找到限制:極小是:{limits . Min},極大是:{limits . Max}" );
}
// 找到限制:極小是 10,極大是 20
元組與 System . Tuple
System . ValueTuple 類型支持的 C# 元組不同於 System . Tuple 類型表示的元組。主要區別如下:
- System . ValueTuple 類型是值類型。System . Tuple 類型是引用類型。
- System . ValueTuple 類型是可變的。System . Tuple 類型是不可變的。
- System . ValueTuple 類型的數據成員是字段。System . Tuple 類型的數據成員是屬性。