網(wǎng)站首頁 編程語言 正文
System.Tuple
?類型是在.NET 4.0中引入的,但是有兩個(gè)明顯的缺點(diǎn):
(1) Tuple 類型是引用類型。
(2) 沒有構(gòu)造函數(shù)支持。
為了解決這些問題,C# 7 引入了新的語言功能以及新的類型。
現(xiàn)在,如果您需要從函數(shù)中返回兩個(gè)值的合并結(jié)果,或者把兩個(gè)值合并到一個(gè)哈希表中,可以使用System.ValueTuple
類型并使用一個(gè)精短的語法來構(gòu)造它們:
// 構(gòu)建元組實(shí)例 var tpl = (1, 2); // 在字典中使用元組 var d = new Dictionary<(int x, int y), (byte a, short b)>(); // 不同名稱的元組是兼容的 d.Add(tpl, (a: 3, b: 4)); // 元組值的語義 if (d.TryGetValue((1, 2), out var r)) { // 解構(gòu)元組忽略第一個(gè)元素 var (_, b) = r; // 使用命名語法和定義名稱 Console.WriteLine($"a: {r.a}, b: {r.Item2}"); }
System.ValueTuple
?類型在.NET Framework 4.7中引入。但是您仍然可以在較低的框架版本中使用這個(gè)功能,這時(shí)候,您必須引用一個(gè)特殊的nuget包:System.ValueTuple。
- 元組聲明的語法與函數(shù)參數(shù)聲明相似:
(Type1 name1, Type2 name2)
。 - 元組的構(gòu)造語法類似于參數(shù)構(gòu)造:
(value1, optionalName: value2)
。 - 兩個(gè)元組具有相同的元素類型,但不同的名稱是兼容(**):
(int a, int b) = (1, 2)
。 - 元組值的語義:?
(1,2).Equals((a: 1, b: 2))
、(1,2).GetHashCode() == (1,2).GetHashCode()
?返回的值均是true
。 - 元組不支持
==
和!=
。在github上有一個(gè)懸而未決的討論:“支持==和!=元組類型”。 - 元組可以被“解構(gòu)”,但只能轉(zhuǎn)換成“變量聲明”,而不能“out var”或
case
語句中轉(zhuǎn)換:var (x, y) = (1,2)
?- OK,?(var x, int y) = (1,2)
?- OK,?dictionary.TryGetValue(key, out var (x, y))
?- not OK,?case var (x, y): break;
?- not OK。 - 元組是可變的:
(int a, int b) x = (1,2); x.a++;
. - 元組元素可以通過名稱(如果提供的話)或通過通用名稱
Item1
、Item2
等來訪問。
我們馬上就會(huì)明白上面幾點(diǎn)。
元組名稱
缺少用戶定義的名稱導(dǎo)致System.Tuple
類型不常用。我們可以將System.Tuple
用作一個(gè)精減方法的實(shí)現(xiàn)細(xì)節(jié),但如果我們需要傳遞它,我更喜歡使用具有描述性屬性名稱的命名類型。新元組功能很好地解決了這個(gè)問題:可以為元組元素指定名稱,而不像匿名類型,即使在不同的程序集中也可以使用這些名稱。
C#編譯器為方法簽名中使用的每個(gè)元組類型指定了一個(gè)特殊的標(biāo)記TupleElementNamesAttribute
:
TupleElementNamesAttribute
標(biāo)記非常特殊,不能在用戶代碼中直接使用。如果您嘗試使用它,編譯器會(huì)報(bào)出錯(cuò)誤。
public (int a, int b) Foo1((int c, int d) a) => a; [return: TupleElementNames(new[] { "a", "b" })] public ValueTupleFoo( [TupleElementNames(new[] { "c", "d" })] ValueTuple a) { return a; }
這有助于IDE和編譯器“檢查”元素名稱,并警告錯(cuò)誤地使用它們:
// 正確: 元組聲明可以跳過元素名稱 (int x, int y) tpl = (1, 2); // 警告: 由于目標(biāo)類型“(int x, int y)”指定了其他名稱或未指定名稱,因此元組元素名稱“a”被忽略。 tpl = (a:1, b:2); // 正確 :元組解構(gòu)忽略元素名稱 var (a, b) = tpl; // x: 2, y: 1. 元組名被忽略 var (y, x) = tpl;
編譯器對(duì)繼承的成員有較強(qiáng)的要求:
public abstract class Base { public abstract (int a, int b) Foo(); public abstract (int, int) Bar(); } public class Derived : Base { // 錯(cuò)誤:替代繼承成員“Base.Foo()”時(shí)無法更改元組元素名稱 public override (int c, int d) Foo() => (1, 2); // 錯(cuò)誤:替代繼承成員“Base.Bar()”時(shí)無法更改元組元素名稱 public override (int a, int b) Bar() => (1, 2); }
常規(guī)方法參數(shù)可以在重寫成員中自由更改,重寫成員中的元組元素名稱應(yīng)該與基本類型中的元素名稱完全匹配。
元素名稱推斷
C# 7.1 引入了一個(gè)額外的增強(qiáng)功能:元素名稱推斷類似于C#為匿名類型所做的推斷。
public void NameInference(int x, int y) { // (int x, int y) var tpl = (x, y); var a = new {X = x, Y = y}; // (int X, int Y) var tpl2 = (a.X, a.Y); }
值語義和可變性
元組是公共字段可變的值類型。這聽起來令人擔(dān)憂,因?yàn)槲覀冎揽勺冎殿愋捅徽J(rèn)為是有害的。這是一個(gè)邪惡的小例子:
var x = new { Items = new List{ 1, 2, 3 }.GetEnumerator() }; while (x.Items.MoveNext()) { Console.WriteLine(x.Items.Current); }
如果運(yùn)行這個(gè)代碼,您會(huì)得到一個(gè)無限循環(huán)。List
是一個(gè)可變值類型,但是Items
是屬性。這意味著x.Items
在每個(gè)循環(huán)迭代中返回原始迭代器的副本,從而導(dǎo)致無限循環(huán)。
但是只有當(dāng)數(shù)據(jù)與行為混合在一起時(shí),可變值類型才是危險(xiǎn)的:枚舉器擁有一個(gè)狀態(tài)(當(dāng)前元素)并具有行為(通過調(diào)用MoveNext方法來推進(jìn)迭代器的能力)。這種組合可能會(huì)導(dǎo)致問題,因?yàn)樵诟北旧险{(diào)用方法而不是在原始實(shí)例上調(diào)用方法,從而導(dǎo)致無效操作。下面是一組由于值類型的隱藏副本而導(dǎo)致不明顯行為的示例:gist。
但可變性問題依然存在:
var tpl = (x: 1, y: 2); var hs = new HashSet<(int x, int y)>(); hs.Add(tpl); tpl.x++; Console.WriteLine(hs.Contains(tpl)); // false
元組在字典中作為鍵是非常有用的,并且由于適當(dāng)?shù)闹嫡Z義可以存儲(chǔ)在哈希表中。但是您不應(yīng)該在集合的不同操作之間改變一個(gè)元組變量的狀態(tài)。
解構(gòu)
雖然元組的構(gòu)造函數(shù)對(duì)于元組來說非常特殊的,但是解構(gòu)非常通用,并且可以與任何類型一起使用。
public static class VersionDeconstrucion { public static void Deconstruct(this Version v, out int major, out int minor, out int build, out int revision) { major = v.Major; minor = v.Minor; build = v.Build; revision = v.Revision; } } var version = Version.Parse("1.2.3.4"); var (major, minor, build, _) = version; // Prints: 1.2.3 Console.WriteLine($"{major}.{minor}.{build}");
解構(gòu)使用“鴨子類型(duck-typing)”的方法:如果編譯器可以找到一個(gè)方法調(diào)用Deconstruct
給定的類型 - 實(shí)例方法或擴(kuò)展方法 - 類型即是可解構(gòu)的。
元組別名
一旦您開始使用元組,很快就會(huì)意識(shí)到想在源代碼的多個(gè)地方“重用”一個(gè)元組類型,但這并沒有什么問題。首先,雖然C#不支持給定類型的全局別名,不過您可以使用“using”別名指令,它會(huì)在一個(gè)文件中創(chuàng)建一個(gè)別名;其次,您不能將元組指定別名:
//您不能這樣做:編譯錯(cuò)誤 using Point = (int x, int y); // 但是您可以這樣做 using SetOfPoints = System.Collections.Generic.HashSet<(int x, int y)>;
github上有一個(gè)關(guān)于“使用指令中的元組類型”的討論。所以,如果您發(fā)現(xiàn)自己在多個(gè)地方使用一個(gè)元組類型,你有兩個(gè)選擇:保持復(fù)制粘貼或創(chuàng)建一個(gè)命名的類型。
命名規(guī)則
下面是一個(gè)有趣的問題:我們應(yīng)該遵循什么命名規(guī)則來處理元組元素?Pascal規(guī)則喜歡ElementName
還是駱峰規(guī)則elementName
?一方面,元組元素應(yīng)該遵循公共成員的命名規(guī)則(即PascalCase),但另一方面,元組只是包含變量的變量,變量應(yīng)該遵循駱峰規(guī)則。
如果元組被用作參數(shù)或方法的返回類型使用PascalCase
規(guī)則,并且如果在函數(shù)中本地創(chuàng)建元組使用camelCase
規(guī)則,可以考慮使用基于用法和使用的不同命名方案。但我更喜歡總是使用camelCase
。
總結(jié)
我發(fā)現(xiàn)元組在日常工作中非常有用。我需要不止一個(gè)函數(shù)返回值,或者我需要把一對(duì)值放入一個(gè)哈希表,或者字典的Key非常復(fù)雜,我需要用另一個(gè)“字段”來擴(kuò)展它。
我甚至使用它們來避免與方法類似的ConcurrentDictionary.TryGetOrAdd
的閉包分配,需要額外的參數(shù)。在許多情況下,狀態(tài)也是一個(gè)元組。
該功能是非常有用的,但我還想看到一些增強(qiáng)功能:
- 全局別名:能夠“命名”一個(gè)元組并在整個(gè)程序集中使用它們。
- 在模式匹配中解構(gòu)一個(gè)元組:
out var
、case var
語法。 - 使用運(yùn)算符
==
進(jìn)行相等比較。
原文鏈接:https://www.cnblogs.com/tdfblog/p/dissecting-the-tuples-in-c-7.html
相關(guān)推薦
- 2022-11-01 Flaks基礎(chǔ)之在URL中添加變量的實(shí)現(xiàn)詳解_python
- 2022-08-05 C語言示例講解for循環(huán)的用法_C 語言
- 2022-06-10 FreeRTOS實(shí)時(shí)操作系統(tǒng)隊(duì)列基礎(chǔ)_操作系統(tǒng)
- 2022-06-16 golang?gorm更新日志執(zhí)行SQL示例詳解_Golang
- 2022-07-04 PyG搭建GCN模型實(shí)現(xiàn)節(jié)點(diǎn)分類GCNConv參數(shù)詳解_python
- 2022-09-18 詳解React?hooks組件通信方法_React
- 2022-04-27 Shell獲取路徑操作(dirname?$0?pwd)的實(shí)現(xiàn)_linux shell
- 2022-02-10 antd table 增加底部合計(jì)行統(tǒng)計(jì)
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲(chǔ)小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯(cuò)誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡(jiǎn)單動(dòng)態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對(duì)象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支