Runtime: Dictionary, List and other Collection should have Empty concept

Created on 24 Jun 2020  路  11Comments  路  Source: dotnet/runtime

Background and Motivation

Like Array.Empty one typically wants an empty version of a collection. We already have as @vcsjones pointed out below:

ImmutableDictionary.Empty Field
ImmutableHashSet.Empty Field
ImmutableList.Empty Field

These are not easily discover-able from the types which most developers start out using e.g. List<T>, Dictionary<TKey,TValue>, HashSet<T>.

There is also the ISet<T> Interface to which one can easily use Enumerable.Empty<T>(); to fulfill.

ICollection<T> implements System.Collections.Generic.IEnumerable<T> and adds Count and IsReadOnly

One can easily use

System.Collections.Generic.ICollection<int> test = (System.Collections.Generic.ICollection<int>)Array.Empty<int>();

However one cannot use

System.Collections.Generic.ICollection<int> test = (System.Collections.Generic.ICollection<int>)Enumerable.Empty<int>(); as it gives a:

>Unable to cast object of type 'System.Linq.EmptyPartition1[System.Int32]' to type 'System.Collections.Generic.ICollection1[System.Int32]'.

See also: Partition.SpeedOpt.cs EmptyPartition<T>

See also: https://github.com/dotnet/runtime/issues/27552

Proposed API

namespace System.Linq
{
     internal sealed class EmptyPartition<TElement> : IPartition<TElement>, IEnumerator<TElement>,  
+ ICollection<TElement>{
+ internal static  readonly EmptyPartition<TElement> Empty => new EmptyPartition<TElement>();
+ ICollection<TElement>.Count => 0;
+ ICollection<TElement>.IsReadyOnly => true;
}
namespace System.Collections.Generic
{
     public interface ICollection<T> : System.Collections.Generic.IEnumerable<T>{
+ ICollection<T> GetDefaultEmptyCollection() => EmptyPartition<T>.Empty;
}

Hopefully this also allows for it to be derived and overridden if required, not sure with DIM's.

Usage Examples

Especially useful once nullable enable is applied, you either have to choose to annotate the types that they may return null or you can use the Empty Sentinel.

Alternative Designs

Default implementation methods on IDictionary, EmptyCollections class or additions to almost all collections.

Make EmptyParition public and point to it's Empty member explicitly from ICollection

Risks

Low , Similar to someone hijacking new or Array Constructor or patching the Empty method itself.

Benefits

It provides a more straight forward way to create non null returning API's and you don't have to create the Sentinel yourself. Albeit is trivial to have such a EmptyCollection class with these required sentinel values.

api-needs-work area-System.Collections

Most helpful comment

All 11 comments

Tagging subscribers to this area: @eiriktsarpalis
Notify danmosemsft if you want to be subscribed.

This was discussed in #24031 and that ImmutableDictionary and ImmutableList have an Empty member that works for IDictionary and IList, respectively.

using System.Collections.Generic;
using System.Collections.Immutable;

public class C {
    public void M() {
        Foo(ImmutableDictionary<string, int>.Empty);
        Foo(ImmutableList<string>.Empty);
    }

    private static void Foo(IDictionary<string, int> x) {
    }

    private static void Foo(IList<string> x) {
    }
}

@juliusfriedman you might consider updating the top post to explicitly list the public API you want (eg including HashSet if you want it) so it can be considered together.

Oh yes. I guess it is rare a empty concrete Dictionary is needed.

@danmosemsft Will do

@jkotas I agree in the aspect of Dictionary as proposed it is a duplicate

I need to think this through a bit and will update the proposal in due course, I feel that ICollection etc should probably support this as well.

My comment overlapped with closing it - I do not have an opinion on whether it is worth reconsidering it. But perhaps the properties on ImmutableDictionary/ImmutableList are not as discoverable as we would like.

Since we also have ToHashSet from Linq I feel like HashSet definitely needs support for an Empty, I feel like it's handled by when the underlying Enumerable is empty and via ImmutableHashSet<T> as @vcsjones indicated.

I agree that the discover-ability may be slightly less than desired and hence why I mentioned default interface implementations.

An alternate design might be an interface IEmpty<T> to which can be added to ICollection<T> etc and where the default implementation could live...

Let me know if you need more from me in any regard.

Thank you all for your time!

If you want to pursue this, you'd want to edit the top-post to be the complete proposal, and reopen.

@danmosemsft, I think I am about done for now, please let me know if you need anything else. I think this may need to be expanded but not quite sure as currently public class Dictionary<TKey,TValue> : System.Collections.Generic.ICollection<System.Collections.Generic.KeyValuePair<TKey,TValue>>.

e.g.

System.Collections.Generic.ICollection<int> test = (System.Collections.Generic.ICollection<int>)new Dictionary<int, int>();
Fails with:

> Unable to cast object of type 'System.Collections.Generic.Dictionary2[System.Int32,System.Int32]' to type 'System.Collections.Generic.ICollection1[System.Int32]'.

Implying System.Collections.Generic.ICollection<System.Collections.Generic.KeyValuePair<TKey,TValue>> might need some additions.

as this works:

System.Collections.Generic.ICollection<System.Collections.Generic.KeyValuePair<int,int>> test = (System.Collections.Generic.ICollection<System.Collections.Generic.KeyValuePair<int,int>>)new Dictionary<int, int>();

I also hate to type it here but I also think that even Object should have some form of Null or NonNull, Empty or Default concept perhaps a combination of many of those but on different interfaces.

Perhaps an abstract base class like

c# //Could also be DefaultBase public abstract class NonNullBase<T> where T : class, new { public static readonly T Default = new T(); public static readonly T Null = null; public virtual T ToNull() => Null; public virtual T FromNull() => Default; }

which would bring parity with never having null (such as with struct) but for reference types as well as determining explicitly how to deal with converting to and from null.

I think there was a brief conversation I had with @GrabYourPitchforks in a previous thread where I proposed an operator on objects for null explicitly but I can't currently find it.

Trying to unpack the OP a bit, it seems to me that the request is to provide "Empty" implementations for IReadOnlyList<T>, IReadOnlySet<T> and IReadOnlyDictionary<TKey, TValue> in a way that is more discoverable than ImmutableCollections? If that is the case it is not clear to me how implementing ICollection<T> in the type returned by Enumerable.Empty<T>() addresses the issue.

The main question then is where should we be hosting these methods? Here's one approach:

namespace System.Collections.Generic
{
+    public static class ReadOnlyList
+    {
+        public static IReadOnlyList<T> Empty { get; }
+    }
}

Although I'm not too convinced that the added complexity of a new static class is worth it. Alternatively, we could consider adding it as a static property in the interfaces directly, since apparently this is now legal. However, to my knowledge there is no precedent of us doing this in BCL public APIs.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

sahithreddyk picture sahithreddyk  路  3Comments

GitAntoinee picture GitAntoinee  路  3Comments

yahorsi picture yahorsi  路  3Comments

omajid picture omajid  路  3Comments

EgorBo picture EgorBo  路  3Comments