C# 7 Tuple and ValueTuple

舊功能筆記,太久沒用有些細節忘記了,被提醒了一波,還是筆記下來好了。

  • Tuple 是 class
  • ValueTuple 是 struct 所以不會被 GC 管控,節省 GC 效能。

實驗程式

  1. TypeCheck 驗證各種使用方式實際上是用到哪種 Tuple
  2. ValueTypeCheck 驗證兩種 Tuple 哪個是 ValueType
  3. Deconstruction 驗證使用 Deconstruction 的方式
  4. Deconstruction_AsIgnoreVariable 驗證使用 ignore variable 方式
public class Program
{

    public Tuple<string, int> SimpleTuple => new Tuple<string, int>("testTuple", 1);

    public (string, int) SimpleValueTuple => ("testValueTuple", 2);

    public (string Name, int Value) ValueTupleName => (Name: "testValueTupleName", Value: 3);

    public ValueTuple<string, int> TypeValueTyple => (Name: "testTypeValueTup", Value: 4);

    public ValueTuple<string, int> TypeValueTypleA => ValueTuple.Create("testTypeValueTup", 4);
}

public class Tests
{
    private Program _sut;

    [SetUp]
    public void Setup()
    {
        _sut = new Program();
    }

    [Test]
    public void TypeCheck()
    {
        Assert.IsTrue(_sut.SimpleTuple.GetType() == typeof(Tuple<string, int>));
        Assert.IsTrue(_sut.SimpleValueTuple.GetType() == typeof(ValueTuple<string, int>));
        Assert.IsTrue(_sut.ValueTupleName.GetType() == typeof(ValueTuple<string, int>));
        Assert.IsTrue(_sut.TypeValueTyple.GetType() == typeof(ValueTuple<string, int>));
        Assert.IsTrue(_sut.TypeValueTypleA.GetType() == typeof(ValueTuple<string, int>));
    }


    [Test]
    public void ValueTypeCheck()
    {
        Assert.IsFalse(_sut.SimpleTuple.GetType().IsValueType);
        Assert.IsTrue(_sut.SimpleValueTuple.GetType().IsValueType);
    }

    [Test]
    public void Deconstruction()
    {
        var (name, value) = _sut.SimpleTuple;
        Assert.AreEqual(name, "testTuple");
        Assert.AreEqual(value, 1);

        (name, value) = _sut.SimpleValueTuple;
        Assert.AreEqual(name, "testValueTuple");
        Assert.AreEqual(value, 2);
    }

    [Test]
    public void Deconstruction_AsIgnoreVariable()
    {
        var (name, _) = _sut.SimpleTuple;
        Assert.AreEqual(name, "testTuple");
        //Assert.AreEqual(_, 1); //Error CS0103  The name '_' does not exist in the current context CSharpValueTuple


        (name, _) = _sut.SimpleValueTuple;
        Assert.AreEqual(name, "testValueTuple");
        //Assert.AreEqual(_, 2); //Error CS0103  The name '_' does not exist in the current context CSharpValueTuple
    }

Benchmark

這邊測試了 Tuple, ValueTuple 的不同使用方式,觀察測試結果,使用 ValueTuple + Deconstruction 會比較快。

  • Summary *

BenchmarkDotNet=v0.12.0, OS=Windows 10.0.19041
AMD Ryzen 7 3700X, 1 CPU, 16 logical and 8 physical cores
.NET Core SDK=3.1.100
[Host] : .NET Core 3.1.0 (CoreCLR 4.700.19.56402, CoreFX 4.700.19.56404), X64 RyuJIT
RyuJitX64 : .NET Core 3.1.0 (CoreCLR 4.700.19.56402, CoreFX 4.700.19.56404), X64 RyuJIT

Jit=RyuJit

MethodJobPlatformCountMeanErrorStdDevRatioRatioSD
IterateValueTypesRyuJitX64X6410071.27 ns0.513 ns0.480 ns1.000.00
IterateReferenceTypesRyuJitX64X64100179.57 ns0.687 ns0.574 ns2.520.02
IterateValueTypesDeconstructorRyuJitX64X6410054.79 ns0.392 ns0.367 ns0.770.01
IterateReferenceTypesDeconstructorRyuJitX64X6410053.14 ns0.517 ns0.484 ns0.750.01
IterateValueTypesRyuJitX64X6410000070,376.91 ns276.061 ns258.228 ns1.000.00
IterateReferenceTypesRyuJitX64X64100000178,247.95 ns643.249 ns570.223 ns2.530.01
IterateValueTypesDeconstructorRyuJitX64X6410000050,637.21 ns225.525 ns210.956 ns0.720.00
IterateReferenceTypesDeconstructorRyuJitX64X6410000052,488.01 ns317.409 ns265.051 ns0.750.01
IterateValueTypesRyuJitX64X64100000008,931,029.35 ns54,181.022 ns48,030.065 ns1.000.00
IterateReferenceTypesRyuJitX64X641000000021,009,876.46 ns155,797.185 ns145,732.783 ns2.350.02
IterateValueTypesDeconstructorRyuJitX64X64100000008,521,969.61 ns163,983.042 ns195,210.146 ns0.950.03
IterateReferenceTypesDeconstructorRyuJitX64X641000000017,867,946.65 ns188,879.313 ns167,436.591 ns2.000.02
Tuple<int, int>[] arrayOfRef;
ValueTuple<int, int>[] arrayOfVal;
//...

[Benchmark(Baseline = true)]
public int IterateValueTypes()
{
    int item1Sum = 0, item2Sum = 0;

    var array = arrayOfVal;
    for (int i = 0; i < array.Length; i++)
    {
        ref ValueTuple<int, int> reference = ref array[i];
        item1Sum += reference.Item1;
        item2Sum += reference.Item2;
    }

    return item1Sum + item2Sum;
}

[Benchmark]
public int IterateReferenceTypes()
{
    int item1Sum = 0, item2Sum = 0;

    var array = arrayOfRef;
    for (int i = 0; i < array.Length; i++)
    {
        ref Tuple<int, int> reference = ref array[i];
        item1Sum += reference.Item1;
        item2Sum += reference.Item2;
    }

    return item1Sum + item2Sum;
}

[Benchmark]
public int IterateValueTypesDeconstructor()
{
    int item1Sum = 0, item2Sum = 0;

    var array = arrayOfVal;
    for (int i = 0; i < array.Length; i++)
    {
        var (item1, item2) = array[i];
        item1Sum += item1;
        item2Sum += item2;
    }

    return item1Sum + item2Sum;
}

[Benchmark]
public int IterateReferenceTypesDeconstructor()
{
    int item1Sum = 0, item2Sum = 0;

    var array = arrayOfRef;
    for (int i = 0; i < array.Length; i++)
    {
        var (item1, item2) = array[i];
        item1Sum += item1;
        item2Sum += item2;
    }

    return item1Sum + item2Sum;
}

References

此次程式碼

Benchmark