From 40682844a4dc520c5bf0077dd2f710e0840decda Mon Sep 17 00:00:00 2001 From: lateapexearlyspeed Date: Fri, 5 May 2023 14:38:07 +0800 Subject: [PATCH 1/4] Initial commit to add ToDictionary() overloads for KeyValuePair and ValueTuple kv. --- src/libraries/System.Linq/ref/System.Linq.cs | 4 ++++ .../System.Linq/src/System/Linq/ToCollection.cs | 12 ++++++++++++ 2 files changed, 16 insertions(+) diff --git a/src/libraries/System.Linq/ref/System.Linq.cs b/src/libraries/System.Linq/ref/System.Linq.cs index a35e99ba3c232..75bcc4d7786fd 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..eaf59b0841d89 100644 --- a/src/libraries/System.Linq/src/System/Linq/ToCollection.cs +++ b/src/libraries/System.Linq/src/System/Linq/ToCollection.cs @@ -29,6 +29,18 @@ public static List ToList(this IEnumerable source) return source is IIListProvider listProvider ? listProvider.ToList() : new List(source); } + public static Dictionary ToDictionary(this IEnumerable> source) where TKey : notnull => + source.ToDictionary(kv => kv.Key, kv => kv.Value, null); + + public static Dictionary ToDictionary(this IEnumerable> source, IEqualityComparer comparer) where TKey : notnull => + source.ToDictionary(kv => kv.Key, kv => kv.Value, comparer); + + public static Dictionary ToDictionary(this IEnumerable<(TKey Key, TValue Value)> source) where TKey : notnull => + source.ToDictionary(vt => vt.Key, vt => vt.Value, null); + + 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); From 7cdd861bbcd7a706f92417dcf7567abc6c069d31 Mon Sep 17 00:00:00 2001 From: lateapexearlyspeed Date: Sun, 7 May 2023 18:39:44 +0800 Subject: [PATCH 2/4] Add xml doc; add tests. --- .../src/System/Linq/ToCollection.cs | 38 +++++ .../System.Linq/tests/ToDictionaryTests.cs | 130 +++++++++++++++++- 2 files changed, 166 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Linq/src/System/Linq/ToCollection.cs b/src/libraries/System.Linq/src/System/Linq/ToCollection.cs index eaf59b0841d89..913d2eabc7a84 100644 --- a/src/libraries/System.Linq/src/System/Linq/ToCollection.cs +++ b/src/libraries/System.Linq/src/System/Linq/ToCollection.cs @@ -29,15 +29,53 @@ 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(kv => kv.Key, kv => kv.Value, 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. public static Dictionary ToDictionary(this IEnumerable> source, IEqualityComparer comparer) where TKey : notnull => source.ToDictionary(kv => kv.Key, kv => kv.Value, 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(vt => vt.Key, vt => vt.Value, 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. 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); 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) From c5038db4052ee2cf026bd8a2854dfc5e4509a443 Mon Sep 17 00:00:00 2001 From: lateapexearlyspeed Date: Tue, 16 May 2023 18:04:20 +0800 Subject: [PATCH 3/4] Fix comments: use Dictionary's ctor for IEnumerable> overload; API signature changed to nullable comparer. --- src/libraries/System.Linq/ref/System.Linq.cs | 4 ++-- .../System.Linq/src/System/Linq/ToCollection.cs | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/libraries/System.Linq/ref/System.Linq.cs b/src/libraries/System.Linq/ref/System.Linq.cs index 75bcc4d7786fd..e49127be6c737 100644 --- a/src/libraries/System.Linq/ref/System.Linq.cs +++ b/src/libraries/System.Linq/ref/System.Linq.cs @@ -204,9 +204,9 @@ public static System.Collections.Generic.IEnumerable< 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> 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<(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 913d2eabc7a84..aa7efd762bf3d 100644 --- a/src/libraries/System.Linq/src/System/Linq/ToCollection.cs +++ b/src/libraries/System.Linq/src/System/Linq/ToCollection.cs @@ -39,7 +39,7 @@ public static List ToList(this IEnumerable source) /// is a null reference. /// contains one or more duplicate keys. public static Dictionary ToDictionary(this IEnumerable> source) where TKey : notnull => - source.ToDictionary(kv => kv.Key, kv => kv.Value, null); + source.ToDictionary(null); /// /// Creates a from an according to specified key comparer. @@ -51,8 +51,8 @@ public static Dictionary ToDictionary(this IEnumerab /// A that contains keys and values from . /// is a null reference. /// contains one or more duplicate keys. - public static Dictionary ToDictionary(this IEnumerable> source, IEqualityComparer comparer) where TKey : notnull => - source.ToDictionary(kv => kv.Key, kv => kv.Value, comparer); + public static Dictionary ToDictionary(this IEnumerable> source, IEqualityComparer? comparer) where TKey : notnull => + new(source, comparer); /// /// Creates a from an according to the default comparer for the key type. @@ -64,7 +64,7 @@ public static Dictionary ToDictionary(this IEnumerab /// 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(vt => vt.Key, vt => vt.Value, null); + source.ToDictionary(null); /// /// Creates a from an according to specified key comparer. @@ -76,7 +76,7 @@ public static Dictionary ToDictionary(this IEnumerab /// A that contains keys and values from . /// is a null reference. /// contains one or more duplicate keys. - public static Dictionary ToDictionary(this IEnumerable<(TKey Key, TValue Value)> source, IEqualityComparer comparer) where TKey : notnull => + 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 => From 9a064267f92ce4d4ae294938a979349774e3ca93 Mon Sep 17 00:00:00 2001 From: lateapexearlyspeed Date: Wed, 17 May 2023 14:53:23 +0800 Subject: [PATCH 4/4] Throw ArgumentNullExp with 'source' parameter name. --- .../System.Linq/src/System/Linq/ToCollection.cs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Linq/src/System/Linq/ToCollection.cs b/src/libraries/System.Linq/src/System/Linq/ToCollection.cs index aa7efd762bf3d..280ced4d34f62 100644 --- a/src/libraries/System.Linq/src/System/Linq/ToCollection.cs +++ b/src/libraries/System.Linq/src/System/Linq/ToCollection.cs @@ -51,8 +51,18 @@ public static Dictionary ToDictionary(this IEnumerab /// A that contains keys and values from . /// is a null reference. /// contains one or more duplicate keys. - public static Dictionary ToDictionary(this IEnumerable> source, IEqualityComparer? comparer) where TKey : notnull => - new(source, comparer); + /// + /// 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. @@ -76,6 +86,9 @@ public static Dictionary ToDictionary(this IEnumerab /// 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);