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

Added AsReadOnly extension methods for IDictionary<T, U> and IList<T> #61172

Merged
2 changes: 2 additions & 0 deletions src/libraries/System.Collections/ref/System.Collections.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ public static partial class CollectionExtensions
public static TValue GetValueOrDefault<TKey, TValue>(this System.Collections.Generic.IReadOnlyDictionary<TKey, TValue> dictionary, TKey key, TValue defaultValue) { throw null; }
public static bool Remove<TKey, TValue>(this System.Collections.Generic.IDictionary<TKey, TValue> dictionary, TKey key, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TValue value) { throw null; }
public static bool TryAdd<TKey, TValue>(this System.Collections.Generic.IDictionary<TKey, TValue> dictionary, TKey key, TValue value) { throw null; }
public static System.Collections.ObjectModel.ReadOnlyCollection<T> AsReadOnly<T>(this IList<T> list) { throw null; }
public static System.Collections.ObjectModel.ReadOnlyDictionary<TKey, TValue> AsReadOnly<TKey, TValue>(this IDictionary<TKey, TValue> dictionary) where TKey : notnull { throw null; }
}
public abstract partial class Comparer<T> : System.Collections.Generic.IComparer<T>, System.Collections.IComparer
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.ObjectModel;
using System.Diagnostics.CodeAnalysis;

namespace System.Collections.Generic
Expand Down Expand Up @@ -55,5 +56,32 @@ public static bool Remove<TKey, TValue>(this IDictionary<TKey, TValue> dictionar
value = default;
return false;
}

/// <summary>
/// Returns a read-only <see cref="ReadOnlyCollection{T}"/> wrapper
/// for the specified list.
/// </summary>
/// <typeparam name="T">The type of elements in the collection.</typeparam>
/// <param name="list">The list to wrap.</param>
/// <returns>An object that acts as a read-only wrapper around the current <see cref="IList{T}"/>.</returns>
/// <exception cref="ArgumentNullException"><paramref name="list"/> is null.</exception>
public static ReadOnlyCollection<T> AsReadOnly<T>(this IList<T> list)
{
return new ReadOnlyCollection<T>(list);
}

/// <summary>
/// Returns a read-only <see cref="ReadOnlyDictionary{TKey, TValue}"/> wrapper
/// for the current dictionary.
/// </summary>
/// <typeparam name="TKey">The type of keys in the dictionary.</typeparam>
/// <typeparam name="TValue">The type of values in the dictionary.</typeparam>
/// <param name="dictionary">The dictionary to wrap.</param>
/// <returns>An object that acts as a read-only wrapper around the current <see cref="IDictionary{TKey, TValue}"/>.</returns>
/// <exception cref="ArgumentNullException"><paramref name="dictionary"/> is null.</exception>
public static ReadOnlyDictionary<TKey, TValue> AsReadOnly<TKey, TValue>(this IDictionary<TKey, TValue> dictionary) where TKey : notnull
{
return new ReadOnlyDictionary<TKey, TValue>(dictionary);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Collections.ObjectModel;
using Xunit;

namespace System.Collections.Tests
Expand Down Expand Up @@ -104,5 +105,39 @@ public void Remove_KeyDoesntExistInIDictionary_ReturnsFalse()
Assert.False(dictionary.Remove("key", out var value));
Assert.Equal(default(string), value);
}

[Fact]
eiriktsarpalis marked this conversation as resolved.
Show resolved Hide resolved
public void AsReadOnly_TurnsIListIntoReadOnlyCollection()
{
IList<string> list = new List<string> { "A", "B" };
ReadOnlyCollection<string> readOnlyCollection = list.AsReadOnly();
Assert.NotNull(readOnlyCollection);
CollectionAsserts.Equal(list, readOnlyCollection);
}

[Fact]
public void AsReadOnly_TurnsIDictionaryIntoReadOnlyDictionary()
{
IDictionary<string, string> dictionary = new Dictionary<string, string> { ["key1"] = "value1", ["key2"] = "value2" };
ReadOnlyDictionary<string, string> readOnlyDictionary = dictionary.AsReadOnly();
Assert.NotNull(readOnlyDictionary);
Assert.Equal(dictionary["key1"], readOnlyDictionary["key1"]);
Assert.Equal(dictionary["key2"], readOnlyDictionary["key2"]);
Assert.Equal(dictionary.Count, readOnlyDictionary.Count);
}

[Fact]
public void AsReadOnly_NullIList_ThrowsArgumentNullException()
{
IList<string> list = null;
Assert.Throws<ArgumentNullException>("list", () => list.AsReadOnly());
}

[Fact]
public void AsReadOnly_NullIDictionary_ThrowsArgumentNullException()
{
IDictionary<string, string> dictionary = null;
Assert.Throws<ArgumentNullException>("dictionary", () => dictionary.AsReadOnly());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Collections.ObjectModel.ReadOnlyDictionary<,>))]
71 changes: 0 additions & 71 deletions src/libraries/System.ObjectModel/ref/System.ObjectModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,77 +43,6 @@ protected virtual void OnPropertyChanged(System.ComponentModel.PropertyChangedEv
protected override void RemoveItem(int index) { }
protected override void SetItem(int index, T item) { }
}
public partial class ReadOnlyDictionary<TKey, TValue> : System.Collections.Generic.ICollection<System.Collections.Generic.KeyValuePair<TKey, TValue>>, System.Collections.Generic.IDictionary<TKey, TValue>, System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<TKey, TValue>>, System.Collections.Generic.IReadOnlyCollection<System.Collections.Generic.KeyValuePair<TKey, TValue>>, System.Collections.Generic.IReadOnlyDictionary<TKey, TValue>, System.Collections.ICollection, System.Collections.IDictionary, System.Collections.IEnumerable where TKey : notnull
{
public ReadOnlyDictionary(System.Collections.Generic.IDictionary<TKey, TValue> dictionary) { }
public int Count { get { throw null; } }
protected System.Collections.Generic.IDictionary<TKey, TValue> Dictionary { get { throw null; } }
public TValue this[TKey key] { get { throw null; } }
public System.Collections.ObjectModel.ReadOnlyDictionary<TKey, TValue>.KeyCollection Keys { get { throw null; } }
bool System.Collections.Generic.ICollection<System.Collections.Generic.KeyValuePair<TKey, TValue>>.IsReadOnly { get { throw null; } }
TValue System.Collections.Generic.IDictionary<TKey, TValue>.this[TKey key] { get { throw null; } set { } }
System.Collections.Generic.ICollection<TKey> System.Collections.Generic.IDictionary<TKey, TValue>.Keys { get { throw null; } }
System.Collections.Generic.ICollection<TValue> System.Collections.Generic.IDictionary<TKey, TValue>.Values { get { throw null; } }
System.Collections.Generic.IEnumerable<TKey> System.Collections.Generic.IReadOnlyDictionary<TKey, TValue>.Keys { get { throw null; } }
System.Collections.Generic.IEnumerable<TValue> System.Collections.Generic.IReadOnlyDictionary<TKey, TValue>.Values { get { throw null; } }
bool System.Collections.ICollection.IsSynchronized { get { throw null; } }
object System.Collections.ICollection.SyncRoot { get { throw null; } }
bool System.Collections.IDictionary.IsFixedSize { get { throw null; } }
bool System.Collections.IDictionary.IsReadOnly { get { throw null; } }
object? System.Collections.IDictionary.this[object key] { get { throw null; } set { } }
System.Collections.ICollection System.Collections.IDictionary.Keys { get { throw null; } }
System.Collections.ICollection System.Collections.IDictionary.Values { get { throw null; } }
public System.Collections.ObjectModel.ReadOnlyDictionary<TKey, TValue>.ValueCollection Values { get { throw null; } }
public bool ContainsKey(TKey key) { throw null; }
public System.Collections.Generic.IEnumerator<System.Collections.Generic.KeyValuePair<TKey, TValue>> GetEnumerator() { throw null; }
void System.Collections.Generic.ICollection<System.Collections.Generic.KeyValuePair<TKey, TValue>>.Add(System.Collections.Generic.KeyValuePair<TKey, TValue> item) { }
void System.Collections.Generic.ICollection<System.Collections.Generic.KeyValuePair<TKey, TValue>>.Clear() { }
bool System.Collections.Generic.ICollection<System.Collections.Generic.KeyValuePair<TKey, TValue>>.Contains(System.Collections.Generic.KeyValuePair<TKey, TValue> item) { throw null; }
void System.Collections.Generic.ICollection<System.Collections.Generic.KeyValuePair<TKey, TValue>>.CopyTo(System.Collections.Generic.KeyValuePair<TKey, TValue>[] array, int arrayIndex) { }
bool System.Collections.Generic.ICollection<System.Collections.Generic.KeyValuePair<TKey, TValue>>.Remove(System.Collections.Generic.KeyValuePair<TKey, TValue> item) { throw null; }
void System.Collections.Generic.IDictionary<TKey, TValue>.Add(TKey key, TValue value) { }
bool System.Collections.Generic.IDictionary<TKey, TValue>.Remove(TKey key) { throw null; }
void System.Collections.ICollection.CopyTo(System.Array array, int index) { }
void System.Collections.IDictionary.Add(object key, object? value) { }
void System.Collections.IDictionary.Clear() { }
bool System.Collections.IDictionary.Contains(object key) { throw null; }
System.Collections.IDictionaryEnumerator System.Collections.IDictionary.GetEnumerator() { throw null; }
void System.Collections.IDictionary.Remove(object key) { }
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; }
public bool TryGetValue(TKey key, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TValue value) { throw null; }
public sealed partial class KeyCollection : System.Collections.Generic.ICollection<TKey>, System.Collections.Generic.IEnumerable<TKey>, System.Collections.Generic.IReadOnlyCollection<TKey>, System.Collections.ICollection, System.Collections.IEnumerable
{
internal KeyCollection() { }
public int Count { get { throw null; } }
bool System.Collections.Generic.ICollection<TKey>.IsReadOnly { get { throw null; } }
bool System.Collections.ICollection.IsSynchronized { get { throw null; } }
object System.Collections.ICollection.SyncRoot { get { throw null; } }
public void CopyTo(TKey[] array, int arrayIndex) { }
public System.Collections.Generic.IEnumerator<TKey> GetEnumerator() { throw null; }
void System.Collections.Generic.ICollection<TKey>.Add(TKey item) { }
void System.Collections.Generic.ICollection<TKey>.Clear() { }
bool System.Collections.Generic.ICollection<TKey>.Contains(TKey item) { throw null; }
bool System.Collections.Generic.ICollection<TKey>.Remove(TKey item) { throw null; }
void System.Collections.ICollection.CopyTo(System.Array array, int index) { }
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; }
}
public sealed partial class ValueCollection : System.Collections.Generic.ICollection<TValue>, System.Collections.Generic.IEnumerable<TValue>, System.Collections.Generic.IReadOnlyCollection<TValue>, System.Collections.ICollection, System.Collections.IEnumerable
{
internal ValueCollection() { }
public int Count { get { throw null; } }
bool System.Collections.Generic.ICollection<TValue>.IsReadOnly { get { throw null; } }
bool System.Collections.ICollection.IsSynchronized { get { throw null; } }
object System.Collections.ICollection.SyncRoot { get { throw null; } }
public void CopyTo(TValue[] array, int arrayIndex) { }
public System.Collections.Generic.IEnumerator<TValue> GetEnumerator() { throw null; }
void System.Collections.Generic.ICollection<TValue>.Add(TValue item) { }
void System.Collections.Generic.ICollection<TValue>.Clear() { }
bool System.Collections.Generic.ICollection<TValue>.Contains(TValue item) { throw null; }
bool System.Collections.Generic.ICollection<TValue>.Remove(TValue item) { throw null; }
void System.Collections.ICollection.CopyTo(System.Array array, int index) { }
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; }
}
}
public partial class ReadOnlyObservableCollection<T> : System.Collections.ObjectModel.ReadOnlyCollection<T>, System.Collections.Specialized.INotifyCollectionChanged, System.ComponentModel.INotifyPropertyChanged
{
public ReadOnlyObservableCollection(System.Collections.ObjectModel.ObservableCollection<T> list) : base (default(System.Collections.Generic.IList<T>)) { }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
</PropertyGroup>
<ItemGroup>
<Compile Include="System.ObjectModel.cs" />
<Compile Include="System.ObjectModel.Forwards.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\System.Runtime\ref\System.Runtime.csproj" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Collections.ObjectModel.ReadOnlyDictionary<,>))]
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<Compile Include="System.ObjectModel.Forwards.cs" />
<Compile Include="System\Collections\CollectionHelpers.cs" />
<Compile Include="System\Collections\Generic\DebugView.cs" />
<Compile Include="System\Collections\Specialized\INotifyCollectionChanged.cs" />
<Compile Include="System\Collections\Specialized\NotifyCollectionChangedAction.cs" />
<Compile Include="System\Collections\Specialized\NotifyCollectionChangedEventArgs.cs" />
<Compile Include="System\Collections\ObjectModel\KeyedCollection.cs" />
<Compile Include="System\Collections\ObjectModel\ObservableCollection.cs" />
<Compile Include="System\Collections\ObjectModel\ReadOnlyDictionary.cs" />
<Compile Include="System\Collections\ObjectModel\ReadOnlyObservableCollection.cs" />
<Compile Include="System\ComponentModel\DataErrorsChangedEventArgs.cs" />
<Compile Include="System\ComponentModel\INotifyDataErrorInfo.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,40 +34,5 @@ internal static void ValidateCopyToArguments(int sourceCount, Array array, int i
throw new ArgumentException(SR.Arg_ArrayPlusOffTooSmall);
}
}

internal static void CopyTo<T>(ICollection<T> collection, Array array, int index)
{
ValidateCopyToArguments(collection.Count, array, index);

if (collection is ICollection nonGenericCollection)
{
// Easy out if the ICollection<T> implements the non-generic ICollection
nonGenericCollection.CopyTo(array, index);
}
else if (array is T[] items)
{
collection.CopyTo(items, index);
}
else
{
// We can't cast array of value type to object[], so we don't support widening of primitive types here.
if (array is not object?[] objects)
{
throw new ArgumentException(SR.Argument_InvalidArrayType, nameof(array));
}

try
{
foreach (T item in collection)
{
objects[index++] = item;
}
}
catch (ArrayTypeMismatchException)
{
throw new ArgumentException(SR.Argument_InvalidArrayType, nameof(array));
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,25 +25,4 @@ public T[] Items
}
}
}

internal sealed class DictionaryDebugView<K, V> where K: notnull
{
private readonly IDictionary<K, V> _dict;

public DictionaryDebugView(IDictionary<K, V> dictionary)
{
_dict = dictionary ?? throw new ArgumentNullException(nameof(dictionary));
}

[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
public KeyValuePair<K, V>[] Items
{
get
{
KeyValuePair<K, V>[] items = new KeyValuePair<K, V>[_dict.Count];
_dict.CopyTo(items, 0);
return items;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,9 @@
<Compile Include="$(MSBuildThisFileDirectory)System\Collections\KeyValuePairs.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Collections\ListDictionaryInternal.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Collections\ObjectModel\Collection.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Collections\ObjectModel\CollectionHelpers.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Collections\ObjectModel\ReadOnlyCollection.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Collections\ObjectModel\ReadOnlyDictionary.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\ComponentModel\DefaultValueAttribute.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\ComponentModel\EditorBrowsableAttribute.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\ComponentModel\EditorBrowsableState.cs" />
Expand Down
Loading