Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ToDictionary() overloads for KeyValuePair and ValueTuple kv. #85811

Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
50 changes: 50 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,56 @@ 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>
public static Dictionary<TKey, TValue> ToDictionary<TKey, TValue>(this IEnumerable<KeyValuePair<TKey, TValue>> source, IEqualityComparer<TKey>? comparer) where TKey : notnull =>
new(source, comparer);
stephentoub marked this conversation as resolved.
Show resolved Hide resolved

/// <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>
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