├── .gitignore ├── LICENSE ├── Readme.md ├── TupleExtensions.sln ├── appveyor.yml ├── src └── TupleExtensions │ ├── TupleExtensions.csproj │ ├── TupleKeyValuePairExtensions.cs │ ├── TupleLinqExtensions.cs │ └── TupleTaskExtensions.cs └── test └── TupleExtensions.Tests ├── TupleExtensionTests.cs └── TupleExtensions.Tests.csproj /.gitignore: -------------------------------------------------------------------------------- 1 | *.*~ 2 | 3 | # User-specific files 4 | *.suo 5 | *.user 6 | *.userosscache 7 | *.sln.docstates 8 | 9 | # Build results 10 | [Dd]ebug/ 11 | [Dd]ebugPublic/ 12 | [Rr]elease/ 13 | [Rr]eleases/ 14 | x64/ 15 | x86/ 16 | build/ 17 | bld/ 18 | [Bb]in/ 19 | [Oo]bj/ 20 | msbuild.log 21 | msbuild.err 22 | msbuild.wrn 23 | 24 | # Visual Studiщ 25 | .vs/ 26 | 27 | # Visual Studio Code 28 | .vscode 29 | 30 | # Built nuget package 31 | *.nupkg -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Victor Gavrish 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | TupleExtensions 2 | --------------- 3 | 4 | `TupleExtensions` is a NuGet package with a number of convenience extensions that leverage C# 7 tuples. 5 | 6 | [![Build status](https://ci.appveyor.com/api/projects/status/l2bwb5ht3sjpaoir?svg=true)](https://ci.appveyor.com/project/VictorGavrish/tupleextensions) [![NuGet](https://img.shields.io/nuget/v/TupleExtensions.VictorGavrish.svg)](https://www.nuget.org/packages/TupleExtensions.VictorGavrish/) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/VictorGavrish/TupleExtensions/blob/master/LICENSE) 7 | 8 | ### Requirements 9 | 10 | This library requires a framework that supports at least version 1.0 of .NET Standard. 11 | 12 | Look [here](https://docs.microsoft.com/en-us/dotnet/articles/standard/library) to find out if your 13 | framework supports it. 14 | 15 | ### Installation 16 | 17 | Run this in your package manager console: 18 | 19 | ``` 20 | Install-Package TupleExtensions.VictorGavrish 21 | ``` 22 | 23 | ### Examples 24 | 25 | An extension on the `KeyValuePair` struct allows ergonomic 26 | dictionary traversal: 27 | 28 | ```csharp 29 | var dictionary = new Dictionary 30 | { 31 | { 1, "one" }, 32 | { 2, "two" } 33 | }; 34 | 35 | foreach ((var key, var value) in dictionary) 36 | { 37 | Console.WriteLine($"{key}:{value}"); 38 | } 39 | ``` 40 | 41 | Prints: 42 | 43 | ``` 44 | 1:one 45 | 2:two 46 | ``` 47 | 48 | ## 49 | 50 | You can await multiple tasks ergonomically: 51 | 52 | ```csharp 53 | var task1 = Task.FromResult(1); 54 | var task2 = Task.FromResult(true); 55 | 56 | var (val1, val2) = await (task1, task2).WhenAll(); 57 | ``` 58 | 59 | ## 60 | 61 | You can do something like this with new and improved `Zip` and `Unzip`: 62 | 63 | ```csharp 64 | var left = new[] { 1, 2, 3 }; 65 | var right = new[] { "two", "three", "four" }; 66 | 67 | var zipped = left.Skip(1).Zip(right); 68 | 69 | foreach ((var lval, var rval) in zipped) 70 | { 71 | Console.WriteLine($"{lval}:{rval}"); 72 | } 73 | 74 | (var newLeft, var newRight) = zipped.Unzip(); 75 | 76 | foreach (var element in newLeft) 77 | { 78 | Console.WriteLine(element); 79 | } 80 | 81 | foreach (var element in newRight) 82 | { 83 | Console.WriteLine(element); 84 | } 85 | ``` 86 | 87 | This prints: 88 | 89 | ``` 90 | 2:two 91 | 3:three 92 | 2 93 | 3 94 | two 95 | three 96 | ``` 97 | 98 | ## 99 | 100 | `WithIndexes` adds indexes to a collection. This allows you to continue to use `foreach` 101 | where previously you'd be tempted to use a `for` loop: 102 | 103 | ```csharp 104 | var array = new[] { "one", "two", "three" }; 105 | foreach ((var index, var element) in array.WithIndexes()) 106 | { 107 | Console.WriteLine($"{index}:{element}"); 108 | } 109 | ``` 110 | 111 | This prints: 112 | 113 | ``` 114 | 0:one 115 | 1:two 116 | 2:three 117 | ``` 118 | 119 | ## 120 | 121 | You can create a dictionary from a sequence of tuples: 122 | 123 | ```csharp 124 | var sequence = new[] 125 | { 126 | (1, "one"), 127 | (2, "two") 128 | }; 129 | var dictionary = sequence.ToDictionary(); 130 | ``` 131 | -------------------------------------------------------------------------------- /TupleExtensions.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26228.9 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TupleExtensions", "src\TupleExtensions\TupleExtensions.csproj", "{F7EF44F3-24FC-47A3-BE56-912B249D5D94}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TupleExtensions.Tests", "test\TupleExtensions.Tests\TupleExtensions.Tests.csproj", "{2CC3DB41-8EAD-4E8F-A044-972B1EF79B82}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{E90EC9A0-6493-449B-B617-626492474C17}" 11 | ProjectSection(SolutionItems) = preProject 12 | appveyor.yml = appveyor.yml 13 | Readme.md = Readme.md 14 | EndProjectSection 15 | EndProject 16 | Global 17 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 18 | Debug|Any CPU = Debug|Any CPU 19 | Release|Any CPU = Release|Any CPU 20 | EndGlobalSection 21 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 22 | {F7EF44F3-24FC-47A3-BE56-912B249D5D94}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {F7EF44F3-24FC-47A3-BE56-912B249D5D94}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {F7EF44F3-24FC-47A3-BE56-912B249D5D94}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {F7EF44F3-24FC-47A3-BE56-912B249D5D94}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {2CC3DB41-8EAD-4E8F-A044-972B1EF79B82}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {2CC3DB41-8EAD-4E8F-A044-972B1EF79B82}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {2CC3DB41-8EAD-4E8F-A044-972B1EF79B82}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {2CC3DB41-8EAD-4E8F-A044-972B1EF79B82}.Release|Any CPU.Build.0 = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | EndGlobal 35 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: '1.3.0.{build}' 2 | image: Visual Studio 2017 3 | configuration: Release 4 | environment: 5 | DOTNET_CLI_TELEMETRY_OPTOUT: 1 6 | build_script: 7 | - dotnet build .\src\TupleExtensions\TupleExtensions.csproj 8 | test_script: 9 | - dotnet test .\test\TupleExtensions.Tests\TupleExtensions.Tests.csproj 10 | artifacts: 11 | - path: '**\*.nupkg' 12 | -------------------------------------------------------------------------------- /src/TupleExtensions/TupleExtensions.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard1.0 5 | TupleExtensions 6 | TupleExtensions 7 | 1.3.0 8 | TupleExtensions.VictorGavrish 9 | Victor Gavrish 10 | A collection of convenience extensions that leverages C# 7 tuples 11 | false 12 | Copyright 2017 (c) Victor Gavrish. All rights are reserved. 13 | linq tuple tuples valuetuple 14 | https://github.com/VictorGavrish/TupleExtensions 15 | True 16 | https://github.com/VictorGavrish/TupleExtensions 17 | https://github.com/VictorGavrish/TupleExtensions/blob/master/LICENSE 18 | Added WhenAll for tuples of Tasks. 19 | 20 | 21 | 22 | bin\Release\netstandard1.0\TupleExtensions.xml 23 | True 24 | 25 | 26 | 27 | 28 | bin\Debug\netstandard1.0\TupleExtensions.xml 29 | True 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /src/TupleExtensions/TupleKeyValuePairExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace TupleExtensions 4 | { 5 | /// 6 | /// Extensions that enhance working with KeyValuePairs by leveraging C# 7 tuples 7 | /// 8 | public static class TupleKeyValuePairExtensions 9 | { 10 | /// 11 | /// Allows to deconstruct a into a (TKey, TValue) tuple. Allows easier traversal 12 | /// of dictionaries within a foreach. 13 | /// 14 | /// The KeyValuePair to deconstruct. 15 | /// The Key part of the KeyValuePair. 16 | /// The Value part of the KeyValuePair. 17 | /// The type of the keys. 18 | /// The type of the values. 19 | /// 20 | /// foreach ((var key, var value) in dictionary) 21 | /// { 22 | /// Console.WriteLine($"{key}:{value}"); 23 | /// } 24 | /// 25 | public static void Deconstruct(this KeyValuePair input, out TKey key, out TValue value) 26 | { 27 | key = input.Key; 28 | value = input.Value; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/TupleExtensions/TupleLinqExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace TupleExtensions 6 | { 7 | /// 8 | /// Extensions that enhance .NET LINQ experience by leveranging tuples from C# 7 9 | /// 10 | public static class TupleLinqExtension 11 | { 12 | /// Adds an index to every element of a sequence. 13 | /// The input sequence. 14 | /// A sequents of tuples that consist of the index of a value in the input iterator plus the value itself. 15 | /// input is null 16 | /// 17 | /// foreach ((var index, var item) in list.WithIndexes()) 18 | /// { 19 | /// Console.WriteLine($"{index}:{item}"); 20 | /// } 21 | /// 22 | public static IEnumerable<(int index, T value)> WithIndexes(this IEnumerable input) 23 | { 24 | if (input == null) 25 | { 26 | throw new ArgumentNullException(nameof(input)); 27 | } 28 | 29 | return input.Select((value, index) => (index, value)); 30 | } 31 | 32 | /// 33 | /// Merges two sequesnces, producing a sequence of tuples. If either of the child sequences stops producing results, 34 | /// the resulting sequence stops producing results as well. 35 | /// 36 | /// The frist sequence to merge. 37 | /// The second sequence to merge. 38 | /// 39 | /// The resulting sequence of tuples. The first element of each tuples comes from the first input sequence, 40 | /// the second element comes from the second input sequence. 41 | /// 42 | /// first or second is null 43 | public static IEnumerable<(V1 left, V2 right)> Zip(this IEnumerable left, IEnumerable right) 44 | { 45 | if (left == null) 46 | { 47 | throw new ArgumentNullException(nameof(left)); 48 | } 49 | 50 | if (right == null) 51 | { 52 | throw new ArgumentNullException(nameof(right)); 53 | } 54 | 55 | return left.Zip(right, (v1, v2) => (v1, v2)); 56 | } 57 | 58 | /// Divides a sequence of tuples into a tuple of two sequences. 59 | /// The sequence to divide. 60 | /// 61 | /// The tuple of sequences. The left seqence will consist of the first elements inside the orirignal sequence's tuple values, 62 | /// the right sequence will consist of the second element inside the orginal sequence's tuple values. 63 | /// 64 | /// input is null 65 | public static (IEnumerable left, IEnumerable right) Unzip(this IEnumerable<(V1 left, V2 right)> input) 66 | { 67 | if (input == null) 68 | { 69 | throw new ArgumentNullException(nameof(input)); 70 | } 71 | 72 | return (input.Select(x => x.left), input.Select(x => x.right)); 73 | } 74 | 75 | /// Creates a from a sequence of tuples. 76 | /// A sequence of tuples to create a from. 77 | /// The type of the keys. 78 | /// The type of the values. 79 | /// A that contains values of type TValue from the input sequence. 80 | /// 81 | /// source is null. 82 | /// -or- 83 | /// there is a null key in the sequence. 84 | /// 85 | /// There are duplicate keys in the sequence. 86 | public static Dictionary ToDictionary(this IEnumerable<(TKey key, TValue value)> source) 87 | { 88 | return ToDictionary(source, null); 89 | } 90 | 91 | /// 92 | /// Creates a from a sequence of tuples 93 | /// according to a specified comparer. 94 | /// 95 | /// A sequence of tuples to create a from. 96 | /// An to compare keys. 97 | /// The type of the keys. 98 | /// The type of the values. 99 | /// A that contains values of type TValue from the input sequence. 100 | /// 101 | /// source is null. 102 | /// -or- 103 | /// there is a null key in the sequence. 104 | /// 105 | /// There are duplicate keys in the sequence. 106 | public static Dictionary ToDictionary(this IEnumerable<(TKey key, TValue value)> source, IEqualityComparer comparer) 107 | { 108 | if (source == null) 109 | { 110 | throw new ArgumentNullException(nameof(source)); 111 | } 112 | 113 | return source.ToDictionary(tup => tup.key, tup => tup.value, comparer); 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/TupleExtensions/TupleTaskExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace TupleExtensions 5 | { 6 | /// 7 | /// Extensions that enhance async/await experience by leveranging tuples from C# 7 8 | /// 9 | public static class TupleTaskExtensions 10 | { 11 | /// 12 | /// Aggregates tuple of tasks into task of . 13 | /// 14 | /// Tuple of , . 15 | /// 16 | public static Task<(T1, T2)> WhenAll(this (Task, Task) tasks) => 17 | GetAggregatedTask(_ => (tasks.Item1.Result, tasks.Item2.Result), tasks.Item1, tasks.Item2); 18 | 19 | /// 20 | /// Aggregates tuple of tasks into task of . 21 | /// 22 | /// Tuple of , , . 23 | /// Task of . 24 | public static Task<(T1, T2, T3)> WhenAll(this (Task, Task, Task) tasks) => 25 | GetAggregatedTask(_ => (tasks.Item1.Result, tasks.Item2.Result, tasks.Item3.Result), tasks.Item1, tasks.Item2, tasks.Item3); 26 | 27 | /// 28 | /// Aggregates tuple of tasks into task of . 29 | /// 30 | /// Tuple of , , , . 31 | /// Task of . 32 | public static Task<(T1, T2, T3, T4)> WhenAll(this (Task, Task, Task, Task) tasks) => 33 | GetAggregatedTask(_ => (tasks.Item1.Result, tasks.Item2.Result, tasks.Item3.Result, tasks.Item4.Result), tasks.Item1, tasks.Item2, tasks.Item3, tasks.Item4); 34 | 35 | /// 36 | /// Aggregates tuple of tasks into task of . 37 | /// 38 | /// Tuple of , , , , . 39 | /// Task of . 40 | public static Task<(T1, T2, T3, T4, T5)> WhenAll(this (Task, Task, Task, Task, Task) tasks) => 41 | GetAggregatedTask(_ => (tasks.Item1.Result, tasks.Item2.Result, tasks.Item3.Result, tasks.Item4.Result, tasks.Item5.Result), tasks.Item1, tasks.Item2, tasks.Item3, tasks.Item4, tasks.Item5); 42 | 43 | 44 | /// 45 | /// Aggregates tuple of tasks into task of . 46 | /// 47 | /// Tuple of , , , , , . 48 | /// Task of . 49 | public static Task<(T1, T2, T3, T4, T5, T6)> WhenAll(this (Task, Task, Task, Task, Task, Task) tasks) => 50 | GetAggregatedTask(_ => (tasks.Item1.Result, tasks.Item2.Result, tasks.Item3.Result, tasks.Item4.Result, tasks.Item5.Result, tasks.Item6.Result), 51 | tasks.Item1, tasks.Item2, tasks.Item3, tasks.Item4, tasks.Item5, tasks.Item6); 52 | 53 | /// 54 | /// Aggregates tuple of tasks into task of . 55 | /// 56 | /// Tuple of , , , , , , . 57 | /// Task of . 58 | public static Task<(T1, T2, T3, T4, T5, T6, T7)> WhenAll(this (Task, Task, Task, Task, Task, Task, Task) tasks) => 59 | GetAggregatedTask(_ => (tasks.Item1.Result, tasks.Item2.Result, tasks.Item3.Result, tasks.Item4.Result, tasks.Item5.Result, tasks.Item6.Result, tasks.Item7.Result), 60 | tasks.Item1, tasks.Item2, tasks.Item3, tasks.Item4, tasks.Item5, tasks.Item6, tasks.Item7); 61 | 62 | /// 63 | /// Aggregates tuple of tasks into task of . 64 | /// 65 | /// Tuple of , , , , , , , . 66 | /// Task of . 67 | public static Task<(T1, T2, T3, T4, T5, T6, T7, T8)> WhenAll(this (Task, Task, Task, Task, Task, Task, Task, Task) tasks) => 68 | GetAggregatedTask(_ => (tasks.Item1.Result, tasks.Item2.Result, tasks.Item3.Result, tasks.Item4.Result, tasks.Item5.Result, tasks.Item6.Result, tasks.Item7.Result, tasks.Item8.Result), 69 | tasks.Item1, tasks.Item2, tasks.Item3, tasks.Item4, tasks.Item5, tasks.Item6, tasks.Item7, tasks.Item8); 70 | 71 | /// 72 | /// Aggregates tuple of tasks into task of . 73 | /// 74 | /// Tuple of , , , , , , , , . 75 | /// Task of . 76 | public static Task<(T1, T2, T3, T4, T5, T6, T7, T8, T9)> WhenAll(this (Task, Task, Task, Task, Task, Task, Task, Task, Task) tasks) => 77 | GetAggregatedTask(_ => (tasks.Item1.Result, tasks.Item2.Result, tasks.Item3.Result, tasks.Item4.Result, tasks.Item5.Result, tasks.Item6.Result, tasks.Item7.Result, tasks.Item8.Result, tasks.Item9.Result), 78 | tasks.Item1, tasks.Item2, tasks.Item3, tasks.Item4, tasks.Item5, tasks.Item6, tasks.Item7, tasks.Item8, tasks.Item9); 79 | 80 | /// 81 | /// Aggregates tuple of tasks into task of . 82 | /// 83 | /// Tuple of , , , , , , , , , . 84 | /// Task of . 85 | public static Task<(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10)> WhenAll(this (Task, Task, Task, Task, Task, Task, Task, Task, Task, Task) tasks) => 86 | GetAggregatedTask(_ => (tasks.Item1.Result, tasks.Item2.Result, tasks.Item3.Result, tasks.Item4.Result, tasks.Item5.Result, tasks.Item6.Result, tasks.Item7.Result, tasks.Item8.Result, tasks.Item9.Result, tasks.Item10.Result), 87 | tasks.Item1, tasks.Item2, tasks.Item3, tasks.Item4, tasks.Item5, tasks.Item6, tasks.Item7, tasks.Item8, tasks.Item9, tasks.Item10); 88 | 89 | private static Task GetAggregatedTask(Func resultSelector, params Task[] tasks) 90 | { 91 | var tcs = new TaskCompletionSource(); 92 | 93 | var aggregatedTask = Task.WhenAll(tasks); 94 | 95 | aggregatedTask.ContinueWith(t => tcs.SetResult(resultSelector(t)), TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.ExecuteSynchronously); 96 | aggregatedTask.ContinueWith(_ => tcs.SetCanceled(), TaskContinuationOptions.OnlyOnCanceled | TaskContinuationOptions.ExecuteSynchronously); 97 | aggregatedTask.ContinueWith(t => tcs.SetException(t.Exception), TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.ExecuteSynchronously); 98 | 99 | return tcs.Task; 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /test/TupleExtensions.Tests/TupleExtensionTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using Xunit; 7 | 8 | namespace TupleExtensions.Tests 9 | { 10 | public class TupleExtensionTests 11 | { 12 | [Fact] 13 | public void TestWithIndexes() 14 | { 15 | // arrange 16 | var original = new[] { "one", "two", "three" }; 17 | var expected = new[] { (0, "one"), (1, "two"), (2, "three") }; 18 | 19 | // act 20 | var actual = original.WithIndexes(); 21 | 22 | // assert 23 | Assert.Equal(expected, actual); 24 | } 25 | 26 | [Fact] 27 | public void TestWithIndexesArgumentNullException() 28 | { 29 | // arrange 30 | IEnumerable empty = null; 31 | 32 | // act 33 | var exception = Record.Exception(() => empty.WithIndexes()); 34 | 35 | // assert 36 | Assert.IsType(exception); 37 | } 38 | 39 | [Fact] 40 | public void TestZip() 41 | { 42 | // arrange 43 | var left = new[] { 1, 2, 3 }; 44 | var right = new[] { "two", "three", "four" }; 45 | var expected = new[] { (2, "two"), (3, "three") }; 46 | 47 | // act 48 | var zipped = left.Skip(1).Zip(right); 49 | 50 | // assert 51 | Assert.Equal(expected, zipped); 52 | } 53 | 54 | [Fact] 55 | public void TestZipArgumentNullExceptions() 56 | { 57 | // arrange 58 | var nonEmpty = new List { "one", "two" }; 59 | IEnumerable empty = null; 60 | 61 | // act 62 | var exception1 = Record.Exception(() => empty.Zip(nonEmpty)); 63 | var exception2 = Record.Exception(() => nonEmpty.Zip(empty)); 64 | 65 | // assert 66 | Assert.IsType(exception1); 67 | Assert.IsType(exception2); 68 | } 69 | 70 | [Fact] 71 | public void TestUnzip() 72 | { 73 | // arrange 74 | var zipped = new[] { (2, "two"), (3, "three") }; 75 | IEnumerable leftExpected = new[] { 2, 3 }; 76 | IEnumerable rightExpected = new[] { "two", "three" }; 77 | 78 | // act 79 | (var leftActual, var rightActual) = zipped.Unzip(); 80 | 81 | // assert 82 | Assert.Equal(leftExpected, leftActual); 83 | Assert.Equal(rightExpected, rightActual); 84 | } 85 | 86 | [Fact] 87 | public void TestUnzipArgumentNullException() 88 | { 89 | // arrange 90 | IEnumerable<(int, string)> empty = null; 91 | 92 | // act 93 | var exception = Record.Exception(() => empty.Unzip()); 94 | 95 | // assert 96 | Assert.IsType(exception); 97 | } 98 | 99 | [Fact] 100 | public void TestDeconstruct() 101 | { 102 | // arrange 103 | const int K = 42; 104 | const string V = "forty two"; 105 | var kvp = new KeyValuePair(K, V); 106 | 107 | // act 108 | (var key, var value) = kvp; 109 | 110 | // assert 111 | Assert.Equal(K, key); 112 | Assert.Equal(V, value); 113 | } 114 | 115 | [Fact] 116 | public void TestDictionaryForeach() 117 | { 118 | // arrange 119 | var dictionary = new Dictionary 120 | { 121 | { 1, "one" }, 122 | { 2, "two" } 123 | }; 124 | var expected = new List<(int, string)> { (1, "one"), (2, "two") }; 125 | 126 | // act 127 | var result = new List<(int, string)>(); 128 | foreach ((var key, var value) in dictionary) 129 | { 130 | result.Add((key, value)); 131 | } 132 | 133 | // assert 134 | Assert.Equal(expected, result); 135 | } 136 | 137 | [Fact] 138 | public void TestToDictionary() 139 | { 140 | // arrange 141 | var sequence = new[] 142 | { 143 | (1, "one"), 144 | (2, "two") 145 | }; 146 | var expected = new Dictionary 147 | { 148 | { 1, "one" }, 149 | { 2, "two" } 150 | }; 151 | 152 | // act 153 | var actual = sequence.ToDictionary(); 154 | 155 | // assert 156 | Assert.Equal(expected, actual); 157 | } 158 | 159 | [Fact] 160 | public void TestToDictionaryWithEqualityComparer() 161 | { 162 | // arrange 163 | var sequence = new[] 164 | { 165 | (1.0f, "one"), 166 | (2.0f, "two") 167 | }; 168 | var expected = new Dictionary 169 | { 170 | { 1.0f, "one" }, 171 | { 2.0f, "two" } 172 | }; 173 | 174 | // act 175 | var actual = sequence.ToDictionary(new TruncatingEqualityComparer()); 176 | 177 | // assert 178 | Assert.Equal(expected, actual); 179 | Assert.Equal("one", actual[1.1f]); 180 | Assert.Throws(() => actual[3.0f]); 181 | } 182 | 183 | [Fact] 184 | public void TestToDictionaryArgumentException() 185 | { 186 | // arrange 187 | var sequence = new[] 188 | { 189 | (1, "one"), 190 | (1, "duplicate") 191 | }; 192 | 193 | // act 194 | var exception = Record.Exception(() => sequence.ToDictionary()); 195 | 196 | // assert 197 | Assert.IsType(exception); 198 | } 199 | 200 | [Fact] 201 | public void TestToDictionaryArgumentNullException() 202 | { 203 | // arrange 204 | IEnumerable<(int, string)> empty = null; 205 | 206 | // act 207 | var exception = Record.Exception(() => empty.ToDictionary()); 208 | 209 | // assert 210 | Assert.IsType(exception); 211 | } 212 | 213 | [Fact] 214 | public async Task TestWaitAll() 215 | { 216 | // arrange 217 | var task1 = Task.FromResult(1); 218 | var task2 = Task.FromResult(true); 219 | 220 | // act 221 | var (val1, val2) = await (task1, task2).WhenAll(); 222 | 223 | // assert 224 | Assert.Equal(task1.Result, val1); 225 | Assert.Equal(task2.Result, val2); 226 | } 227 | 228 | [Fact] 229 | public async Task TestWaitAllArgumentNullException() 230 | { 231 | // arrange 232 | var task1 = Task.FromResult(1); 233 | var task2 = Task.FromException(new ArgumentNullException()); 234 | 235 | // act 236 | var exception = await Record.ExceptionAsync(async () => await (task1, task2).WhenAll()); 237 | 238 | // assert 239 | Assert.IsType(exception); 240 | Assert.IsType(((AggregateException)exception).InnerException); 241 | } 242 | 243 | [Fact] 244 | public async Task TestWaitAllCancelled() 245 | { 246 | // arrange 247 | var task1 = Task.FromResult(1); 248 | var task2 = Task.FromCanceled(new CancellationToken(true)); 249 | 250 | // act 251 | var exception = await Record.ExceptionAsync(async () => await (task1, task2).WhenAll()); 252 | 253 | // assert 254 | Assert.IsType(exception); 255 | } 256 | 257 | private class TruncatingEqualityComparer : IEqualityComparer 258 | { 259 | public bool Equals(float x, float y) 260 | { 261 | return Math.Truncate(x) == Math.Truncate(y); 262 | } 263 | 264 | public int GetHashCode(float obj) 265 | { 266 | return Math.Truncate(obj).GetHashCode(); 267 | } 268 | } 269 | } 270 | } 271 | -------------------------------------------------------------------------------- /test/TupleExtensions.Tests/TupleExtensions.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp1.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | --------------------------------------------------------------------------------