溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

C#中元組類型ValueTuple怎么用

發(fā)布時間:2022-02-22 13:39:18 來源:億速云 閱讀:171 作者:小新 欄目:開發(fā)技術(shù)

這篇文章將為大家詳細(xì)講解有關(guān)C#中元組類型ValueTuple怎么用,小編覺得挺實用的,因此分享給大家做個參考,希望大家閱讀完這篇文章后可以有所收獲。

System.Tuple 類型是在.NET 4.0中引入的,但是有兩個明顯的缺點:
(1) Tuple 類型是引用類型。
(2) 沒有構(gòu)造函數(shù)支持。

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

現(xiàn)在,如果您需要從函數(shù)中返回兩個值的合并結(jié)果,或者把兩個值合并到一個哈希表中,可以使用System.ValueTuple類型并使用一個精短的語法來構(gòu)造它們:

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

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

  • 元組聲明的語法與函數(shù)參數(shù)聲明相似:(Type1 name1, Type2 name2)。

  • 元組的構(gòu)造語法類似于參數(shù)構(gòu)造:(value1, optionalName: value2)。

  • 兩個元組具有相同的元素類型,但不同的名稱是兼容(**):(int a, int b) = (1, 2)

  • 元組值的語義: (1,2).Equals((a: 1, b: 2))、(1,2).GetHashCode() == (1,2).GetHashCode() 返回的值均是true

  • 元組不支持==!=。在github上有一個懸而未決的討論:“支持==和!=元組類型”。

  • 元組可以被“解構(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++;.

  • 元組元素可以通過名稱(如果提供的話)或通過通用名稱Item1Item2等來訪問。

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

元組名稱

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

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

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

    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);
     
    // 警告: 由于目標(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;

編譯器對繼承的成員有較強(qiáng)的要求:

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

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

元素名稱推斷

C# 7.1 引入了一個額外的增強(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)憂,因為我們知道可變值類型被認(rèn)為是有害的。這是一個邪惡的小例子:

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

如果運(yùn)行這個代碼,您會得到一個無限循環(huán)。List<T>.Enumerator是一個可變值類型,但是Items是屬性。這意味著x.Items在每個循環(huán)迭代中返回原始迭代器的副本,從而導(dǎo)致無限循環(huán)。

但是只有當(dāng)數(shù)據(jù)與行為混合在一起時,可變值類型才是危險的:枚舉器擁有一個狀態(tài)(當(dāng)前元素)并具有行為(通過調(diào)用MoveNext方法來推進(jìn)迭代器的能力)。這種組合可能會導(dǎo)致問題,因為在副本上調(diào)用方法而不是在原始實例上調(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義可以存儲在哈希表中。但是您不應(yīng)該在集合的不同操作之間改變一個元組變量的狀態(tài)。

解構(gòu)

雖然元組的構(gòu)造函數(shù)對于元組來說非常特殊的,但是解構(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)”的方法:如果編譯器可以找到一個方法調(diào)用Deconstruct給定的類型 - 實例方法或擴(kuò)展方法 - 類型即是可解構(gòu)的。

元組別名

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

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

github上有一個關(guān)于“使用指令中的元組類型”的討論。所以,如果您發(fā)現(xiàn)自己在多個地方使用一個元組類型,你有兩個選擇:保持復(fù)制粘貼或創(chuàng)建一個命名的類型。

命名規(guī)則

下面是一個有趣的問題:我們應(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)元組在日常工作中非常有用。我需要不止一個函數(shù)返回值,或者我需要把一對值放入一個哈希表,或者字典的Key非常復(fù)雜,我需要用另一個“字段”來擴(kuò)展它。

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

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

  • 全局別名:能夠“命名”一個元組并在整個程序集中使用它們。

  • 在模式匹配中解構(gòu)一個元組:out varcase var語法。

  • 使用運(yùn)算符==進(jìn)行相等比較。

關(guān)于“C#中元組類型ValueTuple怎么用”這篇文章就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,使各位可以學(xué)到更多知識,如果覺得文章不錯,請把它分享出去讓更多的人看到。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI