From 95c83fc389cb1cf9618636da94106070eceda884 Mon Sep 17 00:00:00 2001 From: LateApexEarlySpeed <72254037+lateapexearlyspeed@users.noreply.github.com> Date: Wed, 17 May 2023 22:25:35 +0800 Subject: [PATCH] Add ToDictionary() overloads for KeyValuePair and ValueTuple kv. (#85811) * Initial commit to add ToDictionary() overloads for KeyValuePair and ValueTuple kv. * Add xml doc; add tests. * Fix comments: use Dictionary's ctor for IEnumerable> overload; API signature changed to nullable comparer. * Throw ArgumentNullExp with 'source' parameter name. --- src/libraries/System.Linq/ref/System.Linq.cs | 4 + .../src/System/Linq/ToCollection.cs | 63 +++++++++ .../System.Linq/tests/ToDictionaryTests.cs | 130 +++++++++++++++++- 3 files changed, 195 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Linq/ref/System.Linq.cs b/src/libraries/System.Linq/ref/System.Linq.cs index a35e99ba3c232..e49127be6c737 100644 --- a/src/libraries/System.Linq/ref/System.Linq.cs +++ b/src/libraries/System.Linq/ref/System.Linq.cs @@ -203,6 +203,10 @@ public static System.Collections.Generic.IEnumerable< public static System.Linq.IOrderedEnumerable ThenBy(this System.Linq.IOrderedEnumerable source, System.Func keySelector) { throw null; } public static System.Linq.IOrderedEnumerable ThenBy(this System.Linq.IOrderedEnumerable source, System.Func keySelector, System.Collections.Generic.IComparer? comparer) { throw null; } public static TSource[] ToArray(this System.Collections.Generic.IEnumerable source) { throw null; } + public static System.Collections.Generic.Dictionary ToDictionary(this System.Collections.Generic.IEnumerable> source) where TKey : notnull { throw null; } + public static System.Collections.Generic.Dictionary ToDictionary(this System.Collections.Generic.IEnumerable> source, System.Collections.Generic.IEqualityComparer? comparer) where TKey : notnull { throw null; } + public static System.Collections.Generic.Dictionary ToDictionary(this System.Collections.Generic.IEnumerable<(TKey Key, TValue Value)> source) where TKey : notnull { throw null; } + public static System.Collections.Generic.Dictionary ToDictionary(this System.Collections.Generic.IEnumerable<(TKey Key, TValue Value)> source, System.Collections.Generic.IEqualityComparer? comparer) where TKey : notnull { throw null; } public static System.Collections.Generic.Dictionary ToDictionary(this System.Collections.Generic.IEnumerable source, System.Func keySelector) where TKey : notnull { throw null; } public static System.Collections.Generic.Dictionary ToDictionary(this System.Collections.Generic.IEnumerable source, System.Func keySelector, System.Collections.Generic.IEqualityComparer? comparer) where TKey : notnull { throw null; } public static System.Collections.Generic.Dictionary ToDictionary(this System.Collections.Generic.IEnumerable source, System.Func keySelector, System.Func elementSelector) where TKey : notnull { throw null; } diff --git a/src/libraries/System.Linq/src/System/Linq/ToCollection.cs b/src/libraries/System.Linq/src/System/Linq/ToCollection.cs index 038a47e93160c..280ced4d34f62 100644 --- a/src/libraries/System.Linq/src/System/Linq/ToCollection.cs +++ b/src/libraries/System.Linq/src/System/Linq/ToCollection.cs @@ -29,6 +29,69 @@ public static List ToList(this IEnumerable source) return source is IIListProvider listProvider ? listProvider.ToList() : new List(source); } + /// + /// Creates a from an according to the default comparer for the key type. + /// + /// The type of the keys from elements of + /// The type of the values from elements of + /// The to create a from. + /// A that contains keys and values from and uses default comparer for the key type. + /// is a null reference. + /// contains one or more duplicate keys. + public static Dictionary ToDictionary(this IEnumerable> source) where TKey : notnull => + source.ToDictionary(null); + + /// + /// Creates a from an according to specified key comparer. + /// + /// The type of the keys from elements of + /// The type of the values from elements of + /// The to create a from. + /// An to compare keys. + /// A that contains keys and values from . + /// is a null reference. + /// contains one or more duplicate keys. + /// + /// If is null, the default equality comparer is used to compare keys. + /// + public static Dictionary ToDictionary(this IEnumerable> source, IEqualityComparer? comparer) where TKey : notnull + { + if (source is null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source); + } + + return new(source, comparer); + } + + /// + /// Creates a from an according to the default comparer for the key type. + /// + /// The type of the keys from elements of + /// The type of the values from elements of + /// The to create a from. + /// A that contains keys and values from and uses default comparer for the key type. + /// is a null reference. + /// contains one or more duplicate keys. + public static Dictionary ToDictionary(this IEnumerable<(TKey Key, TValue Value)> source) where TKey : notnull => + source.ToDictionary(null); + + /// + /// Creates a from an according to specified key comparer. + /// + /// The type of the keys from elements of + /// The type of the values from elements of + /// The to create a from. + /// An to compare keys. + /// A that contains keys and values from . + /// is a null reference. + /// contains one or more duplicate keys. + /// + /// If is null, the default equality comparer is used to compare keys. + /// + public static Dictionary ToDictionary(this IEnumerable<(TKey Key, TValue Value)> source, IEqualityComparer? comparer) where TKey : notnull => + source.ToDictionary(vt => vt.Key, vt => vt.Value, comparer); + public static Dictionary ToDictionary(this IEnumerable source, Func keySelector) where TKey : notnull => ToDictionary(source, keySelector, null); diff --git a/src/libraries/System.Linq/tests/ToDictionaryTests.cs b/src/libraries/System.Linq/tests/ToDictionaryTests.cs index c3dcc7a6e42db..5991b28e9920f 100644 --- a/src/libraries/System.Linq/tests/ToDictionaryTests.cs +++ b/src/libraries/System.Linq/tests/ToDictionaryTests.cs @@ -20,6 +20,11 @@ public void ToDictionary_AlwaysCreateACopy() Assert.NotSame(source, result); Assert.Equal(source, result); + + result = source.ToDictionary(); + + Assert.NotSame(source, result); + Assert.Equal(source, result); } @@ -37,6 +42,24 @@ private void RunToDictionaryOnAllCollectionTypes(T[] items, Action(items).ToDictionary(key => key, value => value)); } + private void RunToDictionaryFromKvOnAllCollectionTypes(T[] items, Action> validation) where T : notnull + { + KeyValuePair[] kvps = items.Select(item => new KeyValuePair(item, item)).ToArray(); + + validation(kvps.ToDictionary()); + validation(new List>(kvps).ToDictionary()); + validation(new TestEnumerable>(kvps).ToDictionary()); + validation(new TestReadOnlyCollection>(kvps).ToDictionary()); + validation(new TestCollection>(kvps).ToDictionary()); + + (T, T)[] vts = items.Select(item => (item, item)).ToArray(); + + validation(vts.ToDictionary()); + validation(new List<(T, T)>(vts).ToDictionary()); + validation(new TestEnumerable<(T, T)>(vts).ToDictionary()); + validation(new TestReadOnlyCollection<(T, T)>(vts).ToDictionary()); + validation(new TestCollection<(T, T)>(vts).ToDictionary()); + } [Fact] public void ToDictionary_WorkWithEmptyCollection() @@ -49,6 +72,16 @@ public void ToDictionary_WorkWithEmptyCollection() }); } + [Fact] + public void ToDictionaryFromKv_WorkWithEmptyCollection() + { + RunToDictionaryFromKvOnAllCollectionTypes(Array.Empty(), + resultDictionary => + { + Assert.NotNull(resultDictionary); + Assert.Empty(resultDictionary); + }); + } [Fact] public void ToDictionary_ProduceCorrectDictionary() @@ -72,12 +105,44 @@ public void ToDictionary_ProduceCorrectDictionary() }); } + [Fact] + public void ToDictionaryFromKv_ProduceCorrectDictionary() + { + int[] sourceArray = new[] { 1, 2, 3, 4, 5, 6, 7 }; + RunToDictionaryFromKvOnAllCollectionTypes(sourceArray, + resultDictionary => + { + Assert.Equal(sourceArray.Length, resultDictionary.Count); + Assert.Equal(sourceArray, resultDictionary.Keys); + Assert.Equal(sourceArray, resultDictionary.Values); + }); + + string[] sourceStringArray = new[] { "1", "2", "3", "4", "5", "6", "7", "8" }; + RunToDictionaryFromKvOnAllCollectionTypes(sourceStringArray, + resultDictionary => + { + Assert.Equal(sourceStringArray.Length, resultDictionary.Count); + foreach (string item in sourceStringArray) + { + Assert.Same(item, resultDictionary[item]); + } + }); + } + [Fact] public void RunOnce() { Assert.Equal( new Dictionary {{1, "0"}, {2, "1"}, {3, "2"}, {4, "3"}}, Enumerable.Range(0, 4).RunOnce().ToDictionary(i => i + 1, i => i.ToString())); + + Assert.Equal( + new Dictionary { { 0, "0" }, { 1, "1" }, { 2, "2" }, { 3, "3" } }, + Enumerable.Range(0, 4).Select(i => new KeyValuePair(i, i.ToString())).RunOnce().ToDictionary()); + + Assert.Equal( + new Dictionary { { 0, "0" }, { 1, "1" }, { 2, "2" }, { 3, "3" } }, + Enumerable.Range(0, 4).Select(i => (i, i.ToString())).RunOnce().ToDictionary()); } [Fact] @@ -91,6 +156,12 @@ public void ToDictionary_PassCustomComparer() Dictionary result2 = collection.ToDictionary(key => key, val => val, comparer); Assert.Same(comparer, result2.Comparer); + + Dictionary result3 = collection.Select(i => new KeyValuePair(i, i)).ToDictionary(comparer); + Assert.Same(comparer, result3.Comparer); + + Dictionary result4 = collection.Select(i => (i, i)).ToDictionary(comparer); + Assert.Same(comparer, result4.Comparer); } [Fact] @@ -105,6 +176,24 @@ public void ToDictionary_UseDefaultComparerOnNull() Assert.Same(EqualityComparer.Default, result2.Comparer); } + [Fact] + public void ToDictionary_UseDefaultComparer() + { + TestCollection collection = new TestCollection(new[] { 1, 2, 3, 4, 5, 6 }); + + Dictionary result1 = collection.ToDictionary(key => key); + Assert.Same(EqualityComparer.Default, result1.Comparer); + + Dictionary result2 = collection.ToDictionary(key => key, val => val); + Assert.Same(EqualityComparer.Default, result2.Comparer); + + Dictionary result3 = collection.Select(i => new KeyValuePair(i, i)).ToDictionary(); + Assert.Same(EqualityComparer.Default, result3.Comparer); + + Dictionary result4 = collection.Select(i => (i, i)).ToDictionary(); + Assert.Same(EqualityComparer.Default, result4.Comparer); + } + [Fact] public void ToDictionary_KeyValueSelectorsWork() { @@ -120,8 +209,9 @@ public void ToDictionary_KeyValueSelectorsWork() [Fact] public void ToDictionary_ThrowArgumentNullExceptionWhenSourceIsNull() { - int[] source = null; - AssertExtensions.Throws("source", () => source.ToDictionary(key => key)); + AssertExtensions.Throws("source", () => ((IEnumerable)null).ToDictionary(key => key)); + AssertExtensions.Throws("source", () => ((IEnumerable>)null).ToDictionary()); + AssertExtensions.Throws("source", () => ((IEnumerable<(int, int)>)null).ToDictionary()); } @@ -226,6 +316,24 @@ public void ThrowsOnNullKey() }; AssertExtensions.Throws("key", () => source.ToDictionary(e => e.Name)); + + var source2 = new KeyValuePair[] + { + new("Chris", 50), + new("Bob", 95), + new(default, 55) + }; + + AssertExtensions.Throws("key", () => source2.ToDictionary()); + + var source3 = new[] + { + ("Chris", 50), + ("Bob", 95), + (default, 55) + }; + + AssertExtensions.Throws("key", () => source3.ToDictionary()); } [Fact] @@ -305,6 +413,24 @@ public void ThrowsOnDuplicateKeys() }; AssertExtensions.Throws(null, () => source.ToDictionary(e => e.Name, e => e, new AnagramEqualityComparer())); + + var source2 = new KeyValuePair[] + { + new("Chris", 50), + new("Bob", 95), + new("Bob", 55) + }; + + AssertExtensions.Throws(null, () => source2.ToDictionary(new AnagramEqualityComparer())); + + var source3 = new[] + { + ("Chris", 50), + ("Bob", 95), + ("Bob", 55) + }; + + AssertExtensions.Throws(null, () => source3.ToDictionary(new AnagramEqualityComparer())); } private static void AssertMatches(IEnumerable keys, IEnumerable values, Dictionary dict)