Skip to content

Commit

Permalink
Add ReadOnly{Observable}Collection/Dictionary.Empty (#76764)
Browse files Browse the repository at this point in the history
* Add ReadOnly{Observable}Collection/Dictionary.Empty

* Revert XamlLoadPermission.cs change to fix downlevel build
  • Loading branch information
stephentoub committed Oct 24, 2022
1 parent e232f79 commit bbcff6b
Show file tree
Hide file tree
Showing 37 changed files with 126 additions and 114 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ public static RuntimeTypeInfo CastToRuntimeTypeInfo(this Type type)

public static ReadOnlyCollection<T> ToReadOnlyCollection<T>(this IEnumerable<T> enumeration)
{
return new ReadOnlyCollection<T>(enumeration.ToArray());
return Array.AsReadOnly(enumeration.ToArray());
}

public static MethodInfo FilterAccessor(this MethodInfo accessor, bool nonPublic)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2068,8 +2068,7 @@ private ReadOnlyCollection<TKey> GetKeys()

if (count == 0)
{
// TODO https://github.com/dotnet/runtime/issues/76028: Replace with ReadOnlyCollection<TKey>.Empty.
return Array.Empty<TKey>().AsReadOnly();
return ReadOnlyCollection<TKey>.Empty;
}

var keys = new TKey[count];
Expand Down Expand Up @@ -2110,8 +2109,7 @@ private ReadOnlyCollection<TValue> GetValues()

if (count == 0)
{
// TODO https://github.com/dotnet/runtime/pull/76097: Replace with ReadOnlyCollection<TValue>.Empty.
return Array.Empty<TValue>().AsReadOnly();
return ReadOnlyCollection<TValue>.Empty;
}

var values = new TValue[count];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public static ReadOnlyCollection<T> ToReadOnlyCollection<T>(this IEnumerable<T>
{
ArgumentNullException.ThrowIfNull(source);

return new ReadOnlyCollection<T>(source.AsArray());
return Array.AsReadOnly(source.AsArray());
}

public static IEnumerable<T>? ConcatAllowingNull<T>(this IEnumerable<T>? source, IEnumerable<T>? second)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ internal CompositionException(string? message, Exception? innerException, IEnume
: base(message, innerException)
{
Requires.NullOrNotNullElements(errors, nameof(errors));
_errors = new ReadOnlyCollection<CompositionError>(errors == null ? Array.Empty<CompositionError>() : errors.ToArray<CompositionError>());
_errors = Array.AsReadOnly(errors == null ? Array.Empty<CompositionError>() : errors.ToArray<CompositionError>());
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ public AggregateExportProvider(params ExportProvider[]? providers)
}

_providers = copiedProviders;
_readOnlyProviders = new ReadOnlyCollection<ExportProvider>(_providers);
_readOnlyProviders = Array.AsReadOnly(_providers);
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ public CompositionContainer(ComposablePartCatalog? catalog, CompositionOptions c
_rootProvider.ExportsChanged += OnExportsChangedInternal;
_rootProvider.ExportsChanging += OnExportsChangingInternal;

_providers = (providers != null) ? new ReadOnlyCollection<ExportProvider>((ExportProvider[])providers.Clone()) : EmptyProviders;
_providers = (providers != null) ? Array.AsReadOnly((ExportProvider[])providers.Clone()) : EmptyProviders;
}

internal CompositionOptions CompositionOptions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -632,7 +632,7 @@ public void Refresh()
_catalogCollection.Remove(catalogToRemove.Item2);
}

_loadedFiles = afterFiles.ToReadOnlyCollection();
_loadedFiles = Array.AsReadOnly(afterFiles);

// Lastly complete any changes added to the atomicComposition during the change event
atomicComposition.Complete();
Expand Down Expand Up @@ -756,7 +756,7 @@ private void Initialize(string path, string searchPattern)
_assemblyCatalogs = new Dictionary<string, AssemblyCatalog>();
_catalogCollection = new ComposablePartCatalogCollection(null, null, null);

_loadedFiles = GetFiles().ToReadOnlyCollection();
_loadedFiles = Array.AsReadOnly(GetFiles());

foreach (string file in _loadedFiles)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,6 @@ public SharingBoundaryAttribute(params string[] sharingBoundaryNames)
/// <summary>
/// Boundaries implemented by the created ExportLifetimeContext{T}s.
/// </summary>
public ReadOnlyCollection<string> SharingBoundaryNames => new ReadOnlyCollection<string>(_sharingBoundaryNames);
public ReadOnlyCollection<string> SharingBoundaryNames => Array.AsReadOnly(_sharingBoundaryNames);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,6 @@
<Compile Include="System\Runtime\CompilerServices\TrueReadOnlyCollection.cs" />
<Compile Include="System\Dynamic\Utils\CachedReflectionInfo.cs" />
<Compile Include="System\Dynamic\Utils\CollectionExtensions.cs" />
<Compile Include="System\Dynamic\Utils\EmptyReadOnlyCollection.cs" />
<Compile Include="System\Dynamic\UpdateDelegates.Generated.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,25 +57,26 @@ public static T[] RemoveLast<T>(this T[] array)
/// </summary>
public static ReadOnlyCollection<T> ToReadOnly<T>(this IEnumerable<T>? enumerable)
{
if (enumerable == null)
if (enumerable != null && enumerable != ReadOnlyCollection<T>.Empty)
{
return EmptyReadOnlyCollection<T>.Instance;
}
if (enumerable is TrueReadOnlyCollection<T> troc)
{
return troc;
}

if (enumerable is TrueReadOnlyCollection<T> troc)
{
return troc;
}
if (enumerable is ReadOnlyCollectionBuilder<T> builder)
{
return builder.ToReadOnlyCollection();
}

if (enumerable is ReadOnlyCollectionBuilder<T> builder)
{
return builder.ToReadOnlyCollection();
T[] array = enumerable.ToArray();
if (array.Length != 0)
{
return new TrueReadOnlyCollection<T>(array);
}
}

T[] array = enumerable.ToArray();
return array.Length == 0 ?
EmptyReadOnlyCollection<T>.Instance :
new TrueReadOnlyCollection<T>(array);
return ReadOnlyCollection<T>.Empty;
}

// We could probably improve the hashing here
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ internal virtual ReadOnlyCollection<Expression> GetOrMakeExpressions()

internal virtual ReadOnlyCollection<ParameterExpression> GetOrMakeVariables()
{
return EmptyReadOnlyCollection<ParameterExpression>.Instance;
return ReadOnlyCollection<ParameterExpression>.Empty;
}

/// <summary>
Expand Down Expand Up @@ -913,7 +913,7 @@ public static BlockExpression Block(params Expression[] expressions)
/// <returns>The created <see cref="BlockExpression"/>.</returns>
public static BlockExpression Block(IEnumerable<Expression> expressions)
{
return Block(EmptyReadOnlyCollection<ParameterExpression>.Instance, expressions);
return Block(ReadOnlyCollection<ParameterExpression>.Empty, expressions);
}

/// <summary>
Expand All @@ -936,7 +936,7 @@ public static BlockExpression Block(Type type, params Expression[] expressions)
/// <returns>The created <see cref="BlockExpression"/>.</returns>
public static BlockExpression Block(Type type, IEnumerable<Expression> expressions)
{
return Block(type, EmptyReadOnlyCollection<ParameterExpression>.Instance, expressions);
return Block(type, ReadOnlyCollection<ParameterExpression>.Empty, expressions);
}

/// <summary>
Expand Down Expand Up @@ -1089,7 +1089,7 @@ private static BlockExpression GetOptimizedBlockExpression(IReadOnlyList<Express
{
return expressions.Count switch
{
0 => BlockCore(typeof(void), EmptyReadOnlyCollection<ParameterExpression>.Instance, EmptyReadOnlyCollection<Expression>.Instance),
0 => BlockCore(typeof(void), ReadOnlyCollection<ParameterExpression>.Empty, ReadOnlyCollection<Expression>.Empty),
2 => new Block2(expressions[0], expressions[1]),
3 => new Block3(expressions[0], expressions[1], expressions[2]),
4 => new Block4(expressions[0], expressions[1], expressions[2], expressions[3]),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,16 +64,24 @@ internal HoistedLocals(HoistedLocals? parent, ReadOnlyCollection<ParameterExpres
vars = vars.AddFirst(parent.SelfVariable);
}

Dictionary<Expression, int> indexes = new Dictionary<Expression, int>(vars.Count);
for (int i = 0; i < vars.Count; i++)
if (vars.Count != 0)
{
indexes.Add(vars[i], i);
Dictionary<Expression, int> indexes = new Dictionary<Expression, int>(vars.Count);
for (int i = 0; i < vars.Count; i++)
{
indexes.Add(vars[i], i);
}

Indexes = new ReadOnlyDictionary<Expression, int>(indexes);
}
else
{
Indexes = ReadOnlyDictionary<Expression, int>.Empty;
}

SelfVariable = Expression.Variable(typeof(object[]), name: null);
Parent = parent;
Variables = vars;
Indexes = new ReadOnlyDictionary<Expression, int>(indexes);
SelfVariable = Expression.Variable(typeof(object[]), name: null);
}

internal ParameterExpression? ParentVariable => Parent?.SelfVariable;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ public InvocationExpression0(Expression lambda, Type returnType)

internal override ReadOnlyCollection<Expression> GetOrMakeArguments()
{
return EmptyReadOnlyCollection<Expression>.Instance;
return ReadOnlyCollection<Expression>.Empty;
}

public override Expression GetArgument(int index)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,7 @@ internal override ParameterExpression GetParameter(int index)
throw Error.ArgumentOutOfRange(nameof(index));
}

internal override ReadOnlyCollection<ParameterExpression> GetOrMakeParameters() => EmptyReadOnlyCollection<ParameterExpression>.Instance;
internal override ReadOnlyCollection<ParameterExpression> GetOrMakeParameters() => ReadOnlyCollection<ParameterExpression>.Empty;

internal override Expression<TDelegate> Rewrite(Expression body, ParameterExpression[]? parameters)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ public static ListInitExpression ListInit(NewExpression newExpression, IEnumerab
ReadOnlyCollection<Expression> initializerlist = initializers.ToReadOnly();
if (initializerlist.Count == 0)
{
return new ListInitExpression(newExpression, EmptyReadOnlyCollection<ElementInit>.Instance);
return new ListInitExpression(newExpression, ReadOnlyCollection<ElementInit>.Empty);
}

MethodInfo? addMethod = FindMethod(newExpression.Type, "Add", null, new Expression[] { initializerlist[0] }, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ public override Expression GetArgument(int index)

internal override ReadOnlyCollection<Expression> GetOrMakeArguments()
{
return EmptyReadOnlyCollection<Expression>.Instance;
return ReadOnlyCollection<Expression>.Empty;
}

internal override bool SameArguments(ICollection<Expression>? arguments) =>
Expand Down Expand Up @@ -622,7 +622,7 @@ public override Expression GetArgument(int index)

internal override ReadOnlyCollection<Expression> GetOrMakeArguments()
{
return EmptyReadOnlyCollection<Expression>.Instance;
return ReadOnlyCollection<Expression>.Empty;
}

internal override bool SameArguments(ICollection<Expression>? arguments) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ public static NewExpression New(ConstructorInfo constructor, IEnumerable<Express
{
throw Error.TypeMissingDefaultConstructor(type, nameof(type));
}
return new NewValueTypeExpression(type, EmptyReadOnlyCollection<Expression>.Instance, null);
return new NewValueTypeExpression(type, ReadOnlyCollection<Expression>.Empty, null);
}

[RequiresUnreferencedCode(PropertyFromAccessorRequiresUnreferencedCode)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,11 @@ public T[] ToArray()
/// <returns>A new instance of <see cref="ReadOnlyCollection{T}"/>.</returns>
public ReadOnlyCollection<T> ToReadOnlyCollection()
{
if (_size == 0)
{
return ReadOnlyCollection<T>.Empty;
}

// Can we use the stored array?
T[] items;
if (_size == _items.Length)
Expand Down
9 changes: 3 additions & 6 deletions src/libraries/System.Linq/tests/ToListTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -158,19 +158,16 @@ public void ToList_IListWhereSelect(int[] sourceIntegers, string[] convertedStri
var sourceList = new ReadOnlyCollection<int>(sourceIntegers);
var convertedList = new ReadOnlyCollection<string>(convertedStrings);

var emptyIntegersList = new ReadOnlyCollection<int>(Array.Empty<int>());
var emptyStringsList = new ReadOnlyCollection<string>(Array.Empty<string>());

Assert.Equal(convertedList, sourceList.Select(i => i.ToString()).ToList());

Assert.Equal(sourceList, sourceList.Where(i => true).ToList());
Assert.Equal(emptyIntegersList, sourceList.Where(i => false).ToList());
Assert.Equal(ReadOnlyCollection<int>.Empty, sourceList.Where(i => false).ToList());

Assert.Equal(convertedList, sourceList.Where(i => true).Select(i => i.ToString()).ToList());
Assert.Equal(emptyStringsList, sourceList.Where(i => false).Select(i => i.ToString()).ToList());
Assert.Equal(ReadOnlyCollection<string>.Empty, sourceList.Where(i => false).Select(i => i.ToString()).ToList());

Assert.Equal(convertedList, sourceList.Select(i => i.ToString()).Where(s => s != null).ToList());
Assert.Equal(emptyStringsList, sourceList.Select(i => i.ToString()).Where(s => s == null).ToList());
Assert.Equal(ReadOnlyCollection<string>.Empty, sourceList.Select(i => i.ToString()).Where(s => s == null).ToList());
}

[Fact]
Expand Down
1 change: 1 addition & 0 deletions src/libraries/System.ObjectModel/ref/System.ObjectModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ public partial class ObservableCollection<T> : System.Collections.ObjectModel.Co
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>)) { }
public static new System.Collections.ObjectModel.ReadOnlyObservableCollection<T> Empty { get { throw null; } }
protected virtual event System.Collections.Specialized.NotifyCollectionChangedEventHandler? CollectionChanged { add { } remove { } }
protected virtual event System.ComponentModel.PropertyChangedEventHandler? PropertyChanged { add { } remove { } }
event System.Collections.Specialized.NotifyCollectionChangedEventHandler? System.Collections.Specialized.INotifyCollectionChanged.CollectionChanged { add { } remove { } }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ public ReadOnlyObservableCollection(ObservableCollection<T> list) : base(list)
((INotifyPropertyChanged)Items).PropertyChanged += new PropertyChangedEventHandler(HandlePropertyChanged);
}

/// <summary>Gets an empty <see cref="ReadOnlyObservableCollection{T}"/>.</summary>
/// <value>An empty <see cref="ReadOnlyObservableCollection{T}"/>.</value>
/// <remarks>The returned instance is immutable and will always be empty.</remarks>
public static new ReadOnlyObservableCollection<T> Empty { get; } = new ReadOnlyObservableCollection<T>(new ObservableCollection<T>());

/// <summary>
/// CollectionChanged event (per <see cref="INotifyCollectionChanged" />).
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,14 @@ public static void CtorTests_Negative()
AssertExtensions.Throws<ArgumentNullException>("dictionary", () => new ReadOnlyDictionary<int, string>(null));
}

[Fact]
public static void Empty_Idempotent()
{
Assert.NotNull(ReadOnlyDictionary<string, int>.Empty);
Assert.Equal(0, ReadOnlyDictionary<string, int>.Empty.Count);
Assert.Same(ReadOnlyDictionary<string, int>.Empty, ReadOnlyDictionary<string, int>.Empty);
}

/// <summary>
/// Tests that true is returned when the key exists in the dictionary
/// and false otherwise.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,14 @@ public static void Ctor_Tests_Negative()
AssertExtensions.Throws<ArgumentNullException>("list", () => new ReadOnlyObservableCollection<string>(null));
}

[Fact]
public static void Empty_Idempotent()
{
Assert.NotNull(ReadOnlyObservableCollection<int>.Empty);
Assert.Equal(0, ReadOnlyObservableCollection<int>.Empty.Count);
Assert.Same(ReadOnlyObservableCollection<int>.Empty, ReadOnlyObservableCollection<int>.Empty);
}

[Fact]
public static void GetItemTests()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ public ReadOnlyCollection(IList<T> list)
this.list = list;
}

// TODO https://github.com/dotnet/runtime/issues/76028: Make this public.
/// <summary>Gets an empty <see cref="ReadOnlyCollection{T}"/>.</summary>
/// <value>An empty <see cref="ReadOnlyCollection{T}"/>.</value>
/// <remarks>The returned instance is immutable and will always be empty.</remarks>
internal static ReadOnlyCollection<T> Empty { get; } = new ReadOnlyCollection<T>(Array.Empty<T>());
public static ReadOnlyCollection<T> Empty { get; } = new ReadOnlyCollection<T>(Array.Empty<T>());

public int Count => list.Count;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ public ReadOnlyDictionary(IDictionary<TKey, TValue> dictionary)
m_dictionary = dictionary;
}

/// <summary>Gets an empty <see cref="ReadOnlyDictionary{TKey, TValue}"/>.</summary>
/// <value>An empty <see cref="ReadOnlyDictionary{TKey, TValue}"/>.</value>
/// <remarks>The returned instance is immutable and will always be empty.</remarks>
public static ReadOnlyDictionary<TKey, TValue> Empty { get; } = new ReadOnlyDictionary<TKey, TValue>(new Dictionary<TKey, TValue>());

protected IDictionary<TKey, TValue> Dictionary => m_dictionary;

public KeyCollection Keys => _keys ??= new KeyCollection(m_dictionary.Keys);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ private void InitClassDataContract()
set => _helper.Members = value;
}

public override ReadOnlyCollection<DataMember> DataMembers => (Members == null) ? DataContract.s_emptyDataMemberList : Members.AsReadOnly();
public override ReadOnlyCollection<DataMember> DataMembers => (Members == null) ? ReadOnlyCollection<DataMember>.Empty : Members.AsReadOnly();

internal XmlDictionaryString?[]? ChildElementNamespaces
{
Expand Down
Loading

0 comments on commit bbcff6b

Please sign in to comment.