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

Dictionary, List and other Collection should have Empty concept #38340

Closed
juliusfriedman opened this issue Jun 24, 2020 · 35 comments
Closed

Dictionary, List and other Collection should have Empty concept #38340

juliusfriedman opened this issue Jun 24, 2020 · 35 comments
Labels
api-needs-work API needs work before it is approved, it is NOT ready for implementation area-System.Collections
Milestone

Comments

@juliusfriedman
Copy link
Contributor

juliusfriedman commented Jun 24, 2020

Background and Motivation

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

ImmutableDictionary<TKey,TValue>.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: #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.

@juliusfriedman juliusfriedman added the api-suggestion Early API idea and discussion, it is NOT ready for implementation label Jun 24, 2020
@Dotnet-GitSync-Bot Dotnet-GitSync-Bot added area-System.Collections untriaged New issue has not been triaged by the area owner labels Jun 24, 2020
@ghost
Copy link

ghost commented Jun 24, 2020

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

@vcsjones
Copy link
Member

vcsjones commented Jun 24, 2020

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) {
    }
}

@eiriktsarpalis eiriktsarpalis added this to the Future milestone Jun 24, 2020
@layomia layomia removed the untriaged New issue has not been triaged by the area owner label Jun 24, 2020
@jkotas
Copy link
Member

jkotas commented Jun 24, 2020

Duplicate of #24031

@danmoseley
Copy link
Member

@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.

@jkotas jkotas marked this as a duplicate of #24031 Jun 24, 2020
@jkotas jkotas closed this as completed Jun 24, 2020
@jkotas jkotas reopened this Jun 24, 2020
@danmoseley
Copy link
Member

Oh yes. I guess it is rare a empty concrete Dictionary<K,V> is needed.

@juliusfriedman
Copy link
Contributor Author

@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.

@danmoseley
Copy link
Member

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.

@juliusfriedman
Copy link
Contributor Author

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!

@danmoseley
Copy link
Member

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

@juliusfriedman
Copy link
Contributor Author

juliusfriedman commented Jun 27, 2020

@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

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

@eiriktsarpalis
Copy link
Member

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.

@eiriktsarpalis eiriktsarpalis added api-needs-work API needs work before it is approved, it is NOT ready for implementation and removed api-suggestion Early API idea and discussion, it is NOT ready for implementation labels Nov 24, 2020
@atruskie
Copy link

The search for how to do this leads to the previously closed issue and then to here.

ImmutableDictionary is not (in my opinion) discoverable - and I'm even using ImmutableDictionarys in my project.

Further the ImmutableDictionary solution doesn't work in same case without a cast to Dictionary (say where a method is expecting the concrete dictionary type). At that point it is kind of getting pretty ugly.

var suiteConfigForTool = Suite.ToolConfigs.GetValueOrDefault(Tool.Name, new Dictionary<string, object>(0));

vs (this form is bad)

var suiteConfigForTool = Suite.ToolConfigs.GetValueOrDefault(Tool.Name, ImmutableDictionary<string, object>.Empty.ToDictionary());

vs

// proposed
var suiteConfigForTool = Suite.ToolConfigs.GetValueOrDefault(Tool.Name, Dictionary<string, object>.Empty));

@svick
Copy link
Contributor

svick commented Mar 16, 2021

@atruskie I think adding Dictionary<K, V>.Empty is a bad idea, because I would have the following expectations about the API, which cannot be all satisfied at the same time:

  1. Dictionary<K, V>.Empty always returns an empty dictionary.
  2. Dictionary<K, V>.Empty is a property, so it always returns the same instance.
  3. Dictionary<K, V> is mutable.

The easiest expectation to bypass is #2, by changing the Empty property to a GetEmpty() method, but at that point, I don't see the advantage compared with the constructor.

@mbodm
Copy link

mbodm commented Jun 11, 2021

Hey there,

i am not really sure, if some Dictionary.Empty() is that bad of an idea.

Here is why:

30mins ago, i knew nothing about all that stuff. But i knew Enumerable.Empty<>(). So i typed the following code:

using System.Collections.Generic;
using System.Linq;

#nullable enable

namespace MBODM.WADM.Config
{
    public record Config
    {
        public IEnumerable<Game> Games { get; init; } = Enumerable.Empty<Game>();
        public IDictionary<string, string> Settings { get; init; } = new Dictionary<string, string>();
    }
}

Then i thought:
"Hey, i should do some .Empty() thingy with that dictionary too, cause of memory reusage, singleton and such".

In short:
Do not use new for the dictionary, the same reason why we do not write IEnumerable<Game> games = new List<Game>(); 😏

All well and good so far. Around this time i realized Dictionary has no .Empty() method anywhere.

Next step: Go to interwebz.
Result: This thread (and the previous closed thread, marked as duplicate).

I read thread.
What i got from this thread, was: "Use ImmutableDictionary<>.Empty instead."
Did that.
Now my new code looks like this:

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

#nullable enable

namespace MBODM.WADM.Config
{
    public record Config
    {
        public IEnumerable<Game> Games { get; init; } = Enumerable.Empty<Game>();
        public IDictionary<string, string> Settings { get; init; } = ImmutableDictionary<string, string>.Empty;
    }
}

A friend of mine, not that deep into C#, inspected above code (accidentally) and asked me:
"Why you use an immutable dictionary here?"

I explained it to him and he was very confused. So he asked me, why there is no Dictionary.Empty(), because to him it is totally confusing to see some ImmutableDictionary stuff, used with a dictionary that otherwise is not immutable.

I can understand his confusion. 😄

I told him, that the code even could look like this (as mentioned in the previous, closed thread):

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

#nullable enable

namespace MBODM.WADM.Config
{
    public record Config
    {
        public IEnumerable<Game> Games { get; init; } = Enumerable.Empty<Game>();
        public IDictionary<string, string> Settings { get; init; } = ImmutableDictionary.Create<string, string>();
    }
}

There we even explicitly say Create() of an immutable dictionary. Result: Even more confused! 😆

Side note:
Above examples maybe seem ok, when used with record and init (like it indeed is reality in above examples). But think about (imagine) the confusion, when this is used with class instead record and with { get; set; } properties in a nullable environment (in short: not using the C# 8 non-nullable feature, like i did in example with the #nullable enable line) !!! I was a bit lazy and just not reworked the code back to the code my friend looked at (at this point it indeed was class and { get; set; } properties without the #nullable enable line).

Conclusion:
In my opinion, some Dictionary.Empty() not hurts anyone. But "just" using above stuff (ImmutableDictionary<>.Empty field or ImmutableDictionary.Create<>() method) "could" hurt indeed, by confusing ppls, when using mutable dictionaries. Maybe.

Just my 2cents.

--

An additional comment from said friend:
"Why there is no Enumerable<>.Empty field ??? I am even more confused by Enumerable.Empty<XXX>() together with ImmutableDictionary<XXX>.Empty. One has a generic method, while the other one is used as generic type with a static field. In short: One uses <> in method while the other one uses <> in type. Seems not to be a very straight forward concept."

Was not that easy to disagree. 🥴

@danmoseley
Copy link
Member

I would have the following expectations about the API, which cannot be all satisfied at the same time:

Dictionary<K, V>.Empty always returns an empty dictionary.
Dictionary<K, V>.Empty is a property, so it always returns the same instance.
Dictionary<K, V> is mutable.

Why would you expect it to be mutable? Array<T>.Empty is not (of course, because arrays cannot be resized, but that is where my intuition comes from). On past projects where allocations were important, I have defined my own empty dictionary, which was always an immutable singleton which I replaced if I wanted to add things.

@Joe4evr
Copy link
Contributor

Joe4evr commented Jun 11, 2021

On past projects where allocations were important, I have defined my own empty dictionary, which was always an immutable singleton which I replaced if I wanted to add things.

But you can't expect everyone to abide by that. Dictionary<K, V> is mutable is a fact and that fact means that a publicly accessible singleton allocation is A Bad Idea™️.

@mbodm
Copy link

mbodm commented Jun 11, 2021

@atruskie I think adding Dictionary<K, V>.Empty is a bad idea, because I would have the following expectations about the API, which cannot be all satisfied at the same time:

  1. Dictionary<K, V>.Empty always returns an empty dictionary.
  2. Dictionary<K, V>.Empty is a property, so it always returns the same instance.
  3. Dictionary<K, V> is mutable.

The easiest expectation to bypass is #2, by changing the Empty property to a GetEmpty() method, but at that point, I don't see the advantage compared with the constructor.

Not sure if i fully get it. But as another one also said:

Dictionary<> is mutable. And thats a fact. Imo noone expects anything else.

Also point 1: Yes, should indeed return an empty dict in some form.

So i dont really see the problem here.

Ppl expect from Dictionary<>.Empty() the same they expect from Enumerable<>.Empty().

What do i missing here or what exactly is the problem of „could not satisfied at the same time“ ?

@Clockwork-Muse
Copy link
Contributor

So i dont really see the problem here.

Dictionary<K, V>.Empty() is not shorter than new Dictionary<K, V>() (and, if it's a situation where you can use target-typed new, it's definitely longer). There's no advantage in calling that method - this is in contrast to Enumerable<T>.Empty() and ImmutableDictionary<K, V>.Empty, where they are able to return a singleton cached instance, greatly saving on allocations.

What do i missing here or what exactly is the problem of „could not satisfied at the same time“ ?

This is a reference to conflict between the various points. Since Empty would be a property, the expectation is that multiple accesses return the same instance (that is, Object.ReferenceEquals() returns true). However, since the returned dictionary is expected to be mutable, this prevents such a thing from happening.

"Why there is no Enumerable<>.Empty field ??? I am even more confused by Enumerable.Empty<XXX>() together with ImmutableDictionary<XXX>.Empty. One has a generic method, while the other one is used as generic type with a static field. In short: One uses <> in method while the other one uses <> in type. Seems not to be a very straight forward concept."

Because Enumerable<T> doesn't exist, and has no reason to exist, so the only way around that is to supply a type parameter to the method. This is in contrast with ImmutableDictionary<K, V>, where the expectation is that the class will be instanced.

@mbodm
Copy link

mbodm commented Jun 12, 2021

So i dont really see the problem here.

Dictionary<K, V>.Empty() is not shorter than new Dictionary<K, V>() (and, if it's a situation where you can use target-typed new, it's definitely longer). There's no advantage in calling that method - this is in contrast to Enumerable<T>.Empty() and ImmutableDictionary<K, V>.Empty, where they are able to return a singleton cached instance, greatly saving on allocations.

What do i missing here or what exactly is the problem of „could not satisfied at the same time“ ?

This is a reference to conflict between the various points. Since Empty would be a property, the expectation is that multiple accesses return the same instance (that is, Object.ReferenceEquals() returns true). However, since the returned dictionary is expected to be mutable, this prevents such a thing from happening.

"Why there is no Enumerable<>.Empty field ??? I am even more confused by Enumerable.Empty<XXX>() together with ImmutableDictionary<XXX>.Empty. One has a generic method, while the other one is used as generic type with a static field. In short: One uses <> in method while the other one uses <> in type. Seems not to be a very straight forward concept."

Because Enumerable<T> doesn't exist, and has no reason to exist, so the only way around that is to supply a type parameter to the method. This is in contrast with ImmutableDictionary<K, V>, where the expectation is that the class will be instanced.

Ok, get what you mean. Thx.

Maybe is there some solution a Dictionary.Empty could be a singleton, until .Add() etc. is accessed ?

In my head on one side a Dictionary.Empty is rather useful in complex Linq statements, where you just wanna have the empty dict, so you have something with 0 elements to iterate over. This could be very helpful with Safe-Accses-Stuff (in some form Null Object Pattern) and extension methods, when you dont wanna do complex and ugly null handling with elvis and null coalescing operator (if you know what i mean). On the other side, since its a mutable dict, someone maybe wanna add stuff and so a singleton not works. Maybe it could be done, by return a class implementing IDictionary that way, that the enumeration only uses a singleton, but once the .Add() method is called it allocates a real dict internally. Or something along those lines.

Maybe something like that ?

Ofc Enumerable<T> not exists. I assume Enumerable.Empty<>() was there, long before ImmutableDictionary<>. I assume there is no ImmutableDictionary (without brackets), so also a ImmutableDictionary.Empty<>() is out of reach?

Overall: I understand the reasons why. I just look at it, from a „conceptual“ point of view and there its easier to understand for most users, when it all uses the same concept. But ofc, if its not possible, its not possible.

@Clockwork-Muse
Copy link
Contributor

Maybe is there some solution a Dictionary.Empty could be a singleton, until .Add() etc. is accessed ?

... .Add() returns void, so there's no way for the method to return a new instance, and changing it to do so would be an absolutely massive breaking change. Such behavior would also be extremely surprising, given that most of the time it would return the same instance (or null?). Note that the potential performance hit for the check is probably not a large one, compared to all the other work the method needs to do.

In my head on one side a Dictionary.Empty is rather useful in complex Linq statements, where you just wanna have the empty dict, not iterated over.

... the whole point of LINQ is to iterate over something, so this statement is confusing. From LINQ's point of view, there's nothing special about empty enumerables, or a cached, immutable one. LINQ never mutates anything, so isn't terribly relevant to most of the issues here.

This could be very helpful with Safe-Stuff and extension methods, when you dont wanna do complex and ugly null handling with elvis and null coalescing operator (if you know what i mean).

Until null reference types actually cause compile errors, you can't get away from null checks.

I assume there is no ImmutableDictionary (without brackets), so also a ImmutableDictionary.Empty<>() is out of reach?

It does, actually - the method series is ImmutableDictionary.Create(...). Most of the methods on the class are intended to create immutable dictionaries from other enumerable types.

@Joe4evr
Copy link
Contributor

Joe4evr commented Jun 12, 2021

Until null reference types actually cause compile errors, you can't get away from null checks.

<TreatWarningsAsErrors>true</TreatWarningsAsErrors> 🙃

@danmoseley
Copy link
Member

What about a static property on Dictionary<K, V> named something like ZeroSizedDictionary that returns a singleton type derived from Dictionary<K, V> which is empty and read-only?

@Clockwork-Muse
Copy link
Contributor

Until null reference types actually cause compile errors, you can't get away from null checks.

<TreatWarningsAsErrors>true</TreatWarningsAsErrors> 🙃

Yeah, but that doesn't prevent library consumers from passing null, so until the runtime/compiler completely enforces it the checks are still required.

@Clockwork-Muse
Copy link
Contributor

What about a static property on Dictionary<K, V> named something like ZeroSizedDictionary that returns a singleton type derived from Dictionary<K, V> which is empty and read-only?

I mean... are we doing it so that we can pass it to something taking the concrete Dictionary class? I'm not sure that ZeroSizedDictionary is all that discoverable, and it's a somewhat confusing name if you aren't aware of the constraints of implementation.

@stephentoub
Copy link
Member

a singleton type derived from Dictionary<K, V> which is empty and read-only?

It's also not pay-for-play. Every mutating operation on Dictionary would need to check to see if it was the instance / derived type. We're at the point of counting instructions to optimize Dictionary, with an open PR about removing a single null check per operation. Regressing that in the name of this is unlikely to be worthwhile.

@mbodm
Copy link

mbodm commented Jun 13, 2021

Maybe is there some solution a Dictionary.Empty could be a singleton, until .Add() etc. is accessed ?

... .Add() returns void, so there's no way for the method to return a new instance, and changing it to do so would be an absolutely massive breaking change. Such behavior would also be extremely surprising, given that most of the time it would return the same instance (or null?). Note that the potential performance hit for the check is probably not a large one, compared to all the other work the method needs to do.

In my head on one side a Dictionary.Empty is rather useful in complex Linq statements, where you just wanna have the empty dict, not iterated over.

... the whole point of LINQ is to iterate over something, so this statement is confusing. From LINQ's point of view, there's nothing special about empty enumerables, or a cached, immutable one. LINQ never mutates anything, so isn't terribly relevant to most of the issues here.

This could be very helpful with Safe-Stuff and extension methods, when you dont wanna do complex and ugly null handling with elvis and null coalescing operator (if you know what i mean).

Until null reference types actually cause compile errors, you can't get away from null checks.

I assume there is no ImmutableDictionary (without brackets), so also a ImmutableDictionary.Empty<>() is out of reach?

It does, actually - the method series is ImmutableDictionary.Create(...). Most of the methods on the class are intended to create immutable dictionaries from other enumerable types.

Understand. Thx.

Some comments, just for the sake of completeness:

I assume the LINQ iteration part was confusing for you, just cause of my bad english. I have serious problems in such discussions to precisely word my thoughts. Sorry! I just tried to say, that some Dictionary.Empty<> would be as same useful in (complexer) LINQ statements, as a Enumerable.Empty<>.

In example, when using Null Object Pattern, to prevent yourself from complex and very hard-to-read LINQ code, heavily spiked with null coalescing and elvis operator.

Things along these lines:

...(list?.Select(x => /* do stuff */) ?? Enumerable.Empty<XYZ>()).Select(....

or replacing/supporting above code by using Null Object Pattern. In both cases you use Enumerable.Empty. Imagine this with Dictionary, you are looking for some Dictionary.Empty.

Sorry, typed on an iPhone. Can not better explain it at the moment. But its not that terrible important. Was just a use case, where a Dictionary.Empty could be used/wished.

All in all i understand what you mean and do not really disagree.

@mbodm
Copy link

mbodm commented Jun 13, 2021

a singleton type derived from Dictionary<K, V> which is empty and read-only?

It's also not pay-for-play. Every mutating operation on Dictionary would need to check to see if it was the instance / derived type. We're at the point of counting instructions to optimize Dictionary, with an open PR about removing a single null check per operation. Regressing that in the name of this is unlikely to be worthwhile.

Yeah, can definetely imagine this myself. I slowly come to the conclusion that its some sort of a „natural“ problem. Ppl superficiantly just watch out for some Dictionary.Empty, while at the same time it contradicts the fact that a Dictionary is mutable and any form of implementation, that tries to solve this, is way overboard when it comes down to costs vs usefulness.

Its just a totally another thing, than Enumerable.Empty<> for the sole fact, that an Enumerable is immutable, means it can not be changed via .Add() etc. and is just there, so elements could be iterated. And so it is easy to provide an „unchangable“ singleton, we can iterate over. I assume some List.Empty would have the same consequences/problems, as a Dictionary.Empty.

Not sure if some straight „empty-concept“ (as mentioned in OP) could be ever well done in framework. Maybe, in regards to actual NRT stuff and the upcoming future, this is something we consider years later from now as something that the language itself has to deal with. Dont know... Maybe Mads has some opinion here on that topic :)

@Clockwork-Muse
Copy link
Contributor

I just tried to say, that some Dictionary.Empty<> would be as same useful in (complexer) LINQ statements, as a Enumerable.Empty<>

If you're going to be passing it to LINQ to read from, you could pass Enumerable.Empty<KeyValuePair<K, V>>(), which is what Dictionary<K, V> looks like.

@mbodm
Copy link

mbodm commented Jun 14, 2021

I just tried to say, that some Dictionary.Empty<> would be as same useful in (complexer) LINQ statements, as a Enumerable.Empty<>

If you're going to be passing it to LINQ to read from, you could pass Enumerable.Empty<KeyValuePair<K, V>>(), which is what Dictionary<K, V> looks like.

yep, this seems to work for most cases.

@mbodm
Copy link

mbodm commented Jun 14, 2021

Stupid question coming to my mind: Why we even watch out for an Empty concept ? I assume for the sole fact, that we knew we have to use something else than new List(), new Dictionary() etc. because some „new“ will allocate memory (every time we reach that code).

Lets just imagine, compiler and/or framework always, for any type of collection, reuse a singleton or cached thing internally when allocating (doing a new), until we write to the memory (by doing an Add-Access i.e.) and then start real writing and allocating. Just internally. Not in the sense that we get another reference before and after we do some .Add(). Not in the sense of above text, when Stephen stated the „pay for play“ part. More in the sense of „we always can just do new() and compiler will do fine and most efficient“.

I just wanna say: Maybe this is not a language or framework concern, but the compilers job internally. And we not even should think about it ever.

Dont get me wrong. I have zero clue how this all internally works (in heavy contrast to Stephen Toub 😄). This is not a suggestion in any form. I just asked myself, if a C# programmer even should think about performance/allocation, when working with collections.

Maybe, in a perfect world, we should just write new List() or new Dict() everywhere, never thinking about Empty concepts and stuff, cause compiler will do it fine for us.

But i assume things simply not work this way, otherwise they implemented that years ago.

@eiriktsarpalis
Copy link
Member

Echoing other commenters in the issue, I don't think exposing Empty singletons for concrete mutable collection types makes a lot of sense, using the provided constructors provides a simpler and less ambiguous way for initializing empty instances. I believe this is why the OP requests we do this for interface types.

@Clockwork-Muse
Copy link
Contributor

Not in the sense of above text, when Stephen stated the „pay for play“ part.

Because all this does is move the "pay for play" check around a bit, and almost certainly isn't worth the effort on the compiler/runtime side.

@mbodm
Copy link

mbodm commented Jun 15, 2021

Not in the sense of above text, when Stephen stated the „pay for play“ part.

Because all this does is move the "pay for play" check around a bit, and almost certainly isn't worth the effort on the compiler/runtime side.

yes, indeed. can definetely imagine this by myself.

@eiriktsarpalis
Copy link
Member

Exposing Empty properties in mutable collections like Dictionary and List is something we won't be considering, per earlier feedback in this thread. It might make sense to consider Empty properties for ReadOnlyCollection<T> and ReadOnlyDictionary<TKey, TValue>, however I don't believe these improve discoverability compared to the existing immutable collection APIs. I'd be inclined to close this issue, and we can consider potential alternative proposals in the future.

@ghost ghost locked as resolved and limited conversation to collaborators Nov 28, 2021
@stephentoub
Copy link
Member

It might make sense to consider Empty properties for ReadOnlyCollection and ReadOnlyDictionary<TKey, TValue> [...], and we can consider potential alternative proposals in the future.

#76028

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
api-needs-work API needs work before it is approved, it is NOT ready for implementation area-System.Collections
Projects
None yet
Development

No branches or pull requests