Skip to content

Commit

Permalink
Add ToDictionary() overloads for KeyValuePair and ValueTuple kv. (#85811
Browse files Browse the repository at this point in the history
)

* Initial commit to add ToDictionary() overloads for KeyValuePair and ValueTuple kv.

* Add xml doc; add tests.

* Fix comments: use Dictionary's ctor for IEnumerable<KeyValuePair<>> overload; API signature changed to nullable comparer.

* Throw ArgumentNullExp with 'source' parameter name.
  • Loading branch information
lateapexearlyspeed authored May 17, 2023
1 parent 709444f commit 95c83fc
Show file tree
Hide file tree
Showing 3 changed files with 195 additions and 2 deletions.
4 changes: 4 additions & 0 deletions src/libraries/System.Linq/ref/System.Linq.cs
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,10 @@ public static System.Collections.Generic.IEnumerable<
public static System.Linq.IOrderedEnumerable<TSource> ThenBy<TSource, TKey>(this System.Linq.IOrderedEnumerable<TSource> source, System.Func<TSource, TKey> keySelector) { throw null; }
public static System.Linq.IOrderedEnumerable<TSource> ThenBy<TSource, TKey>(this System.Linq.IOrderedEnumerable<TSource> source, System.Func<TSource, TKey> keySelector, System.Collections.Generic.IComparer<TKey>? comparer) { throw null; }
public static TSource[] ToArray<TSource>(this System.Collections.Generic.IEnumerable<TSource> source) { throw null; }
public static System.Collections.Generic.Dictionary<TKey, TValue> ToDictionary<TKey, TValue>(this System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<TKey, TValue>> source) where TKey : notnull { throw null; }
public static System.Collections.Generic.Dictionary<TKey, TValue> ToDictionary<TKey, TValue>(this System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<TKey, TValue>> source, System.Collections.Generic.IEqualityComparer<TKey>? comparer) where TKey : notnull { throw null; }
public static System.Collections.Generic.Dictionary<TKey, TValue> ToDictionary<TKey, TValue>(this System.Collections.Generic.IEnumerable<(TKey Key, TValue Value)> source) where TKey : notnull { throw null; }
public static System.Collections.Generic.Dictionary<TKey, TValue> ToDictionary<TKey, TValue>(this System.Collections.Generic.IEnumerable<(TKey Key, TValue Value)> source, System.Collections.Generic.IEqualityComparer<TKey>? comparer) where TKey : notnull { throw null; }
public static System.Collections.Generic.Dictionary<TKey, TSource> ToDictionary<TSource, TKey>(this System.Collections.Generic.IEnumerable<TSource> source, System.Func<TSource, TKey> keySelector) where TKey : notnull { throw null; }
public static System.Collections.Generic.Dictionary<TKey, TSource> ToDictionary<TSource, TKey>(this System.Collections.Generic.IEnumerable<TSource> source, System.Func<TSource, TKey> keySelector, System.Collections.Generic.IEqualityComparer<TKey>? comparer) where TKey : notnull { throw null; }
public static System.Collections.Generic.Dictionary<TKey, TElement> ToDictionary<TSource, TKey, TElement>(this System.Collections.Generic.IEnumerable<TSource> source, System.Func<TSource, TKey> keySelector, System.Func<TSource, TElement> elementSelector) where TKey : notnull { throw null; }
Expand Down
63 changes: 63 additions & 0 deletions src/libraries/System.Linq/src/System/Linq/ToCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,69 @@ public static List<TSource> ToList<TSource>(this IEnumerable<TSource> source)
return source is IIListProvider<TSource> listProvider ? listProvider.ToList() : new List<TSource>(source);
}

/// <summary>
/// Creates a <see cref="Dictionary{TKey,TValue}"/> from an <see cref="IEnumerable{T}"/> according to the default comparer for the key type.
/// </summary>
/// <typeparam name="TKey">The type of the keys from elements of <paramref name="source"/></typeparam>
/// <typeparam name="TValue">The type of the values from elements of <paramref name="source"/></typeparam>
/// <param name="source">The <see cref="IEnumerable{T}"/> to create a <see cref="Dictionary{TKey,TValue}"/> from.</param>
/// <returns>A <see cref="Dictionary{TKey,TValue}"/> that contains keys and values from <paramref name="source"/> and uses default comparer for the key type.</returns>
/// <exception cref="ArgumentNullException"><paramref name="source"/> is a null reference.</exception>
/// <exception cref="ArgumentException"><paramref name="source"/> contains one or more duplicate keys.</exception>
public static Dictionary<TKey, TValue> ToDictionary<TKey, TValue>(this IEnumerable<KeyValuePair<TKey, TValue>> source) where TKey : notnull =>
source.ToDictionary(null);

/// <summary>
/// Creates a <see cref="Dictionary{TKey,TValue}"/> from an <see cref="IEnumerable{T}"/> according to specified key comparer.
/// </summary>
/// <typeparam name="TKey">The type of the keys from elements of <paramref name="source"/></typeparam>
/// <typeparam name="TValue">The type of the values from elements of <paramref name="source"/></typeparam>
/// <param name="source">The <see cref="IEnumerable{T}"/> to create a <see cref="Dictionary{TKey,TValue}"/> from.</param>
/// <param name="comparer">An <see cref="IEqualityComparer{TKey}"/> to compare keys.</param>
/// <returns>A <see cref="Dictionary{TKey,TValue}"/> that contains keys and values from <paramref name="source"/>.</returns>
/// <exception cref="ArgumentNullException"><paramref name="source"/> is a null reference.</exception>
/// <exception cref="ArgumentException"><paramref name="source"/> contains one or more duplicate keys.</exception>
/// <remarks>
/// If <paramref name="comparer"/> is null, the default equality comparer <see cref="EqualityComparer{TKey}.Default"/> is used to compare keys.
/// </remarks>
public static Dictionary<TKey, TValue> ToDictionary<TKey, TValue>(this IEnumerable<KeyValuePair<TKey, TValue>> source, IEqualityComparer<TKey>? comparer) where TKey : notnull
{
if (source is null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source);
}

return new(source, comparer);
}

/// <summary>
/// Creates a <see cref="Dictionary{TKey,TValue}"/> from an <see cref="IEnumerable{T}"/> according to the default comparer for the key type.
/// </summary>
/// <typeparam name="TKey">The type of the keys from elements of <paramref name="source"/></typeparam>
/// <typeparam name="TValue">The type of the values from elements of <paramref name="source"/></typeparam>
/// <param name="source">The <see cref="IEnumerable{T}"/> to create a <see cref="Dictionary{TKey,TValue}"/> from.</param>
/// <returns>A <see cref="Dictionary{TKey,TValue}"/> that contains keys and values from <paramref name="source"/> and uses default comparer for the key type.</returns>
/// <exception cref="ArgumentNullException"><paramref name="source"/> is a null reference.</exception>
/// <exception cref="ArgumentException"><paramref name="source"/> contains one or more duplicate keys.</exception>
public static Dictionary<TKey, TValue> ToDictionary<TKey, TValue>(this IEnumerable<(TKey Key, TValue Value)> source) where TKey : notnull =>
source.ToDictionary(null);

/// <summary>
/// Creates a <see cref="Dictionary{TKey,TValue}"/> from an <see cref="IEnumerable{T}"/> according to specified key comparer.
/// </summary>
/// <typeparam name="TKey">The type of the keys from elements of <paramref name="source"/></typeparam>
/// <typeparam name="TValue">The type of the values from elements of <paramref name="source"/></typeparam>
/// <param name="source">The <see cref="IEnumerable{T}"/> to create a <see cref="Dictionary{TKey,TValue}"/> from.</param>
/// <param name="comparer">An <see cref="IEqualityComparer{TKey}"/> to compare keys.</param>
/// <returns>A <see cref="Dictionary{TKey,TValue}"/> that contains keys and values from <paramref name="source"/>.</returns>
/// <exception cref="ArgumentNullException"><paramref name="source"/> is a null reference.</exception>
/// <exception cref="ArgumentException"><paramref name="source"/> contains one or more duplicate keys.</exception>
/// <remarks>
/// If <paramref name="comparer"/> is null, the default equality comparer <see cref="EqualityComparer{TKey}.Default"/> is used to compare keys.
/// </remarks>
public static Dictionary<TKey, TValue> ToDictionary<TKey, TValue>(this IEnumerable<(TKey Key, TValue Value)> source, IEqualityComparer<TKey>? comparer) where TKey : notnull =>
source.ToDictionary(vt => vt.Key, vt => vt.Value, comparer);

public static Dictionary<TKey, TSource> ToDictionary<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector) where TKey : notnull =>
ToDictionary(source, keySelector, null);

Expand Down
130 changes: 128 additions & 2 deletions src/libraries/System.Linq/tests/ToDictionaryTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}


Expand All @@ -37,6 +42,24 @@ private void RunToDictionaryOnAllCollectionTypes<T>(T[] items, Action<Dictionary
validation(new TestCollection<T>(items).ToDictionary(key => key, value => value));
}

private void RunToDictionaryFromKvOnAllCollectionTypes<T>(T[] items, Action<Dictionary<T, T>> validation) where T : notnull
{
KeyValuePair<T, T>[] kvps = items.Select(item => new KeyValuePair<T, T>(item, item)).ToArray();

validation(kvps.ToDictionary());
validation(new List<KeyValuePair<T, T>>(kvps).ToDictionary());
validation(new TestEnumerable<KeyValuePair<T, T>>(kvps).ToDictionary());
validation(new TestReadOnlyCollection<KeyValuePair<T, T>>(kvps).ToDictionary());
validation(new TestCollection<KeyValuePair<T, T>>(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()
Expand All @@ -49,6 +72,16 @@ public void ToDictionary_WorkWithEmptyCollection()
});
}

[Fact]
public void ToDictionaryFromKv_WorkWithEmptyCollection()
{
RunToDictionaryFromKvOnAllCollectionTypes(Array.Empty<int>(),
resultDictionary =>
{
Assert.NotNull(resultDictionary);
Assert.Empty(resultDictionary);
});
}

[Fact]
public void ToDictionary_ProduceCorrectDictionary()
Expand All @@ -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<int, string> {{1, "0"}, {2, "1"}, {3, "2"}, {4, "3"}},
Enumerable.Range(0, 4).RunOnce().ToDictionary(i => i + 1, i => i.ToString()));

Assert.Equal(
new Dictionary<int, string> { { 0, "0" }, { 1, "1" }, { 2, "2" }, { 3, "3" } },
Enumerable.Range(0, 4).Select(i => new KeyValuePair<int, string>(i, i.ToString())).RunOnce().ToDictionary());

Assert.Equal(
new Dictionary<int, string> { { 0, "0" }, { 1, "1" }, { 2, "2" }, { 3, "3" } },
Enumerable.Range(0, 4).Select(i => (i, i.ToString())).RunOnce().ToDictionary());
}

[Fact]
Expand All @@ -91,6 +156,12 @@ public void ToDictionary_PassCustomComparer()

Dictionary<int, int> result2 = collection.ToDictionary(key => key, val => val, comparer);
Assert.Same(comparer, result2.Comparer);

Dictionary<int, int> result3 = collection.Select(i => new KeyValuePair<int, int>(i, i)).ToDictionary(comparer);
Assert.Same(comparer, result3.Comparer);

Dictionary<int, int> result4 = collection.Select(i => (i, i)).ToDictionary(comparer);
Assert.Same(comparer, result4.Comparer);
}

[Fact]
Expand All @@ -105,6 +176,24 @@ public void ToDictionary_UseDefaultComparerOnNull()
Assert.Same(EqualityComparer<int>.Default, result2.Comparer);
}

[Fact]
public void ToDictionary_UseDefaultComparer()
{
TestCollection<int> collection = new TestCollection<int>(new[] { 1, 2, 3, 4, 5, 6 });

Dictionary<int, int> result1 = collection.ToDictionary(key => key);
Assert.Same(EqualityComparer<int>.Default, result1.Comparer);

Dictionary<int, int> result2 = collection.ToDictionary(key => key, val => val);
Assert.Same(EqualityComparer<int>.Default, result2.Comparer);

Dictionary<int, int> result3 = collection.Select(i => new KeyValuePair<int, int>(i, i)).ToDictionary();
Assert.Same(EqualityComparer<int>.Default, result3.Comparer);

Dictionary<int, int> result4 = collection.Select(i => (i, i)).ToDictionary();
Assert.Same(EqualityComparer<int>.Default, result4.Comparer);
}

[Fact]
public void ToDictionary_KeyValueSelectorsWork()
{
Expand All @@ -120,8 +209,9 @@ public void ToDictionary_KeyValueSelectorsWork()
[Fact]
public void ToDictionary_ThrowArgumentNullExceptionWhenSourceIsNull()
{
int[] source = null;
AssertExtensions.Throws<ArgumentNullException>("source", () => source.ToDictionary(key => key));
AssertExtensions.Throws<ArgumentNullException>("source", () => ((IEnumerable<int>)null).ToDictionary(key => key));
AssertExtensions.Throws<ArgumentNullException>("source", () => ((IEnumerable<KeyValuePair<int, int>>)null).ToDictionary());
AssertExtensions.Throws<ArgumentNullException>("source", () => ((IEnumerable<(int, int)>)null).ToDictionary());
}


Expand Down Expand Up @@ -226,6 +316,24 @@ public void ThrowsOnNullKey()
};

AssertExtensions.Throws<ArgumentNullException>("key", () => source.ToDictionary(e => e.Name));

var source2 = new KeyValuePair<string?, int>[]
{
new("Chris", 50),
new("Bob", 95),
new(default, 55)
};

AssertExtensions.Throws<ArgumentNullException>("key", () => source2.ToDictionary());

var source3 = new[]
{
("Chris", 50),
("Bob", 95),
(default, 55)
};

AssertExtensions.Throws<ArgumentNullException>("key", () => source3.ToDictionary());
}

[Fact]
Expand Down Expand Up @@ -305,6 +413,24 @@ public void ThrowsOnDuplicateKeys()
};

AssertExtensions.Throws<ArgumentException>(null, () => source.ToDictionary(e => e.Name, e => e, new AnagramEqualityComparer()));

var source2 = new KeyValuePair<string, int>[]
{
new("Chris", 50),
new("Bob", 95),
new("Bob", 55)
};

AssertExtensions.Throws<ArgumentException>(null, () => source2.ToDictionary(new AnagramEqualityComparer()));

var source3 = new[]
{
("Chris", 50),
("Bob", 95),
("Bob", 55)
};

AssertExtensions.Throws<ArgumentException>(null, () => source3.ToDictionary(new AnagramEqualityComparer()));
}

private static void AssertMatches<K, E>(IEnumerable<K> keys, IEnumerable<E> values, Dictionary<K, E> dict)
Expand Down

0 comments on commit 95c83fc

Please sign in to comment.