解析 C# 7中的元組類型(ValueTuple)

来源:http://www.cnblogs.com/tdfblog/archive/2017/11/06/dissecting-the-tuples-in-c-7.html
-Advertisement-
Play Games

System.Tuple 類型是在.NET 4.0中引入的,但是有兩個明顯的缺點:Tuple 類型是引用類型;沒有構造函數支持。為瞭解決這些問題,C# 7 引入了新的語言功能以及新的類型(*)。 ...


System.Tuple 類型是在.NET 4.0中引入的,但是有兩個明顯的缺點:

(1) Tuple 類型是引用類型。

(2) 沒有構造函數支持。

為瞭解決這些問題,C# 7 引入了新的語言功能以及新的類型(*)。

現在,如果您需要從函數中返回兩個值的合併結果,或者把兩個值合併到一個哈希表中,可以使用System.ValueTuple類型並使用一個精短的語法來構造它們:

    // 構建元組實例
    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))
    {
        // 解構元組忽略第一個元素
        var (_, b) = r;
                    
        // 使用命名語法和定義名稱
        Console.WriteLine($"a: {r.a}, b: {r.Item2}");
    }

(*) System.ValueTuple 類型在.NET Framework 4.7中引入。但是您仍然可以在較低的框架版本中使用這個功能,這時候,您必須引用一個特殊的nuget包:System.ValueTuple

  • 元組聲明的語法與函數參數聲明相似:(Type1 name1, Type2 name2)
  • 元組的構造語法類似於參數構造:(value1, optionalName: value2)
  • 兩個元組具有相同的元素類型,但不同的名稱是相容(**):(int a, int b) = (1, 2)
  • 元組值的語義: (1,2).Equals((a: 1, b: 2))(1,2).GetHashCode() == (1,2).GetHashCode() 返回的值均是true
  • 元組不支持==!=。在github上有一個懸而未決的討論:“支持==和!=元組類型”
  • 元組可以被“解構”,但只能轉換成“變數聲明”,而不能“out var”或case語句中轉換: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++;.
  • 元組元素可以通過名稱(如果提供的話)或通過通用名稱Item1Item2等來訪問。

(**) 我們馬上就會明白上面幾點。

元組名稱

缺少用戶定義的名稱導致System.Tuple類型不常用。我們可以將System.Tuple用作一個精減方法的實現細節,但如果我們需要傳遞它,我更喜歡使用具有描述性屬性名稱的命名類型。新元組功能很好地解決了這個問題:可以為元組元素指定名稱,而不像匿名類型,即使在不同的程式集中也可以使用這些名稱。

C#編譯器為方法簽名中使用的每個元組類型指定了一個特殊的標記TupleElementNamesAttribute(***) :

(***)TupleElementNamesAttribute標記非常特殊,不能在用戶代碼中直接使用。如果您嘗試使用它,編譯器會報出錯誤。

    public (int a, int b) Foo1((int c, int d) a) => a;
 
    [return: TupleElementNames(new[] { "a", "b" })]
    public ValueTuple<int, int> Foo(
        [TupleElementNames(new[] { "c", "d" })] ValueTuple<int, int> a)
    {
        return a;
    }

這有助於IDE和編譯器“檢查”元素名稱,並警告錯誤地使用它們:

    // 正確: 元組聲明可以跳過元素名稱
    (int x, int y) tpl = (1, 2);
     
    // 警告: 由於目標類型“(int x, int y)”指定了其他名稱或未指定名稱,因此元組元素名稱“a”被忽略。
    tpl = (a:1, b:2);
     
    // 正確 :元組解構忽略元素名稱
    var (a, b) = tpl;
     
    // x: 2, y: 1. 元組名被忽略
    var (y, x) = tpl;

編譯器對繼承的成員有較強的要求:

    public abstract class Base
    {
        public abstract (int a, int b) Foo();
        public abstract (int, int) Bar();
    }
     
    public class Derived : Base
    {
        // 錯誤:替代繼承成員“Base.Foo()”時無法更改元組元素名稱
        public override (int c, int d) Foo() => (1, 2);
        // 錯誤:替代繼承成員“Base.Bar()”時無法更改元組元素名稱
        public override (int a, int b) Bar() => (1, 2);
    }

常規方法參數可以在重寫成員中自由更改,重寫成員中的元組元素名稱應該與基本類型中的元素名稱完全匹配。

元素名稱推斷

C# 7.1 引入了一個額外的增強功能:元素名稱推斷類似於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);
    }

值語義和可變性

元組是公共欄位可變的值類型。這聽起來令人擔憂,因為我們知道可變值類型被認為是有害的。這是一個邪惡的小例子:

    var x = new { Items = new List<int> { 1, 2, 3 }.GetEnumerator() };
    while (x.Items.MoveNext())
    {
        Console.WriteLine(x.Items.Current);
    }

如果運行這個代碼,您會得到一個無限迴圈。List&lt;T&gt;.Enumerator是一個可變值類型,但是Items是屬性。這意味著x.Items在每個迴圈迭代中返回原始迭代器的副本,從而導致無限迴圈。

但是只有當數據與行為混合在一起時,可變值類型才是危險的:枚舉器擁有一個狀態(當前元素)並具有行為(通過調用MoveNext方法來推進迭代器的能力)。這種組合可能會導致問題,因為在副本上調用方法而不是在原始實例上調用方法,從而導致無效操作。下麵是一組由於值類型的隱藏副本而導致不明顯行為的示例: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

元組在字典中作為鍵是非常有用的,並且由於適當的值語義可以存儲在哈希表中。但是您不應該在集合的不同操作之間改變一個元組變數的狀態。

解構

雖然元組的構造函數對於元組來說非常特殊的,但是解構非常通用,並且可以與任何類型一起使用。

    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}");

解構使用“鴨子類型(duck-typing)”的方法:如果編譯器可以找到一個方法調用Deconstruct給定的類型 - 實例方法或擴展方法 - 類型即是可解構的。

元組別名

一旦您開始使用元組,很快就會意識到想在源代碼的多個地方“重用”一個元組類型,但這並沒有什麼問題。首先,雖然C#不支持給定類型的全局別名,不過您可以使用“using”別名指令,它會在一個文件中創建一個別名;其次,您不能將元組指定別名:

//您不能這樣做:編譯錯誤
using Point = (int x, int y);
 
// 但是您可以這樣做
using SetOfPoints = System.Collections.Generic.HashSet<(int x, int y)>;

github上有一個關於“使用指令中的元組類型”的討論。所以,如果您發現自己在多個地方使用一個元組類型,你有兩個選擇:保持複製粘貼或創建一個命名的類型。

命名規則

下麵是一個有趣的問題:我們應該遵循什麼命名規則來處理元組元素?Pascal規則喜歡ElementName還是駱峰規則elementName?一方面,元組元素應該遵循公共成員的命名規則(即PascalCase),但另一方面,元組只是包含變數的變數,變數應該遵循駱峰規則。

如果元組被用作參數或方法的返回類型使用PascalCase規則,並且如果在函數中本地創建元組使用camelCase規則,可以考慮使用基於用法和使用的不同命名方案。但我更喜歡總是使用camelCase

總結

我發現元組在日常工作中非常有用。我需要不止一個函數返回值,或者我需要把一對值放入一個哈希表,或者字典的Key非常複雜,我需要用另一個“欄位”來擴展它。

我甚至使用它們來避免與方法類似的ConcurrentDictionary.TryGetOrAdd的閉包分配,需要額外的參數。在許多情況下,狀態也是一個元組。

該功能是非常有用的,但我還想看到一些增強功能:

  1. 全局別名:能夠“命名”一個元組併在整個程式集中使用它們(****)。
  2. 在模式匹配中解構一個元組:out varcase var語法。
  3. 使用運算符==進行相等比較。

(****)我知道這些功能是有爭議的,但我認為它非常有用的。我們可以等待Record類型,但還不確定Record是值類型還是引用類型。

原文:《Dissecting the tuples in C# 7》https://blogs.msdn.microsoft.com/seteplia/2017/11/01/dissecting-the-tuples-in-c-7/
翻譯:Sweet Tang
本文地址:http://www.cnblogs.com/tdfblog/p/dissecting-the-tuples-in-c-7.html
歡迎轉載,請在明顯位置給出出處及鏈接。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 最近開發一個新項目,使用了asp.net core 2.0,採用webapi開發後臺,postgresql為資料庫。最先來的問題就是上傳文件的問題。 POST文件的一些坑 使用預設模板創建webapi的controller後,post請求,預設有 請求使用了 標記,用來指示用請求體里獲得數據。 對於 ...
  • 本次是結合近乎免費源碼版產品代碼進行的介紹,關於近乎產品下載可以訪問:www.jinhusns.com 下載免費源碼版本瞭解。 ...
  • 一、Model層 二、控制器層 1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Web; 5 using System.Web.Mvc; 6 using Mvc_Demo. ...
  • 首先,寫這篇文章的原因是因為最近某一個項目中的介面被人為調用了,導致了資料庫數據被串改。雖然是內部人無意點的,但還是引起了我的擔憂,所有整理了下關於Webapi的相關簽名機制。 一、我們在開發介面時,有時候嫌麻煩就懶進行相關的驗證或只進行一些簡單的驗證,這樣客戶端就可以直接調用:如 調用Webapi ...
  • --DateTime 數字型 System.DateTime currentTime=new System.DateTime(); 取當前年月日時分秒 currentTime=System.DateTime.Now; 取當前年 int 年=currentTime.Year; 取當前月 int 月=c ...
  • 在web.config 配置文件增加以下節點配置信息: <!--支持跨域--> <system.webServer> <httpProtocol> <customHeaders> <!-- 指定請求的方式 --> <add name="Access-Control-Allow-Methods" va ...
  • 之前在開發中只用到List的時候幾乎就是拿過來就用,從來沒有考慮過List的記憶體分配問題,試想一個有10萬元素的List的在構造和添加元素時記憶體是如何變化的呢?在MSDN上關於List的Capacity屬性是這麼解釋的,也就是說,當我們添加的元素數量小於等於Capacity的值時,List是不會重新 ...
  • 為了防止不提供原網址的轉載,特在這裡加上原文鏈接: http://www.cnblogs.com/skabyy/p/7295533.html ABP是 ASP.NET Boilerplate Project 的簡稱。ABP是基於 DDD (領域驅動設計)的框架。ABP包含眾多組件,包括依賴註入、動態 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...