-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
[API Proposal]: LinkedDictionary #64829
Comments
Tagging subscribers to this area: @dotnet/area-system-collections Issue DetailsBackground and motivationThis data container is an analogue of LinkedHashMap implementation in Java, which is missing in C#. In the latest release was finally added PriorityQueue which was in Java since 2004. Bridging the gap between APIs of C# and Java will make possible quickly migrate the code from one language to another. Dictionary with an opportunity to traversal the items in the addition order can be super useful in such scenarios as cache with LRU replacement policy or another cases where we want to maintain insertion order. This is not a generic API Proposalnamespace System.Collections.Generic
{
public class LinkedDictionary<TKey, TValue> : IDictionary<TKey, TValue>, IDictionary where TKey : notnull
{
public LinkedDictionary();
public LinkedDictionary(int capacity);
public LinkedDictionary(IEqualityComparer<TKey> comparer);
public LinkedDictionary(int capacity, IEqualityComparer<TKey> comparer);
public LinkedDictionary(IDictionary<TKey, TValue> dictionary);
public LinkedDictionary(IDictionary<TKey, TValue> dictionary, IEqualityComparer<TKey> comparer);
public LinkedDictionary(IEnumerable<KeyValuePair<TKey, TValue>> collection);
public LinkedDictionary(IEnumerable<KeyValuePair<TKey, TValue>> collection, IEqualityComparer<TKey> comparer);
public TValue this[TKey key] { get; set; }
public object this[object key] { get; set; }
public ICollection<TKey> Keys { get; }
public ICollection<TValue> Values { get; }
public int Count { get; }
public bool IsReadOnly { get; }
public bool IsFixedSize { get; }
public bool IsSynchronized { get; }
public object SyncRoot { get; }
ICollection IDictionary.Keys { get; }
ICollection IDictionary.Values { get; }
public void Add(TKey key, TValue value);
public void Add(KeyValuePair<TKey, TValue> item);
public void Add(object key, object value);
public void Clear();
public bool Contains(KeyValuePair<TKey, TValue> item);
public bool Contains(object key);
public bool ContainsKey(TKey key);
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex);
public void CopyTo(Array array, int index);
public TValue GetEldest();
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator();
public void Remove(TKey key);
public bool Remove(KeyValuePair<TKey, TValue> item);
public void Remove(object key);
public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value);
IEnumerator IEnumerable.GetEnumerator();
IDictionaryEnumerator IDictionary.GetEnumerator();
bool IDictionary<TKey, TValue>.Remove(TKey key);
// New APIs
public KeyValuePair<TKey, TValue>GetLast(); // O(1)
public KeyValuePair<TKey, TValue>GetFirst(); // O(1)
public bool RemoveLast(); // O(1)
public bool RemoveFirst(); // O(1)
}
} API Usagevar ld = new LinkedDictionary<int, string>();
ld.Add(3, "three");
ld.Add(2, "two");
ld.Add(1, "one");
Console.WriteLine(ld.GetFirst().Key);
Console.WriteLine(ld.GetLast().Key);
foreach (var item in ld)
Console.WriteLine(item.Key);
ld.RemoveFirst();
ld.RenoveLast();
Console.WriteLine(ld.GetFirst().Key);
Console.WriteLine(ld.GetLast().Key);
/* This code example produces the following output:
3
1
3
2
1
2
2
*/ Alternative DesignsNo response RisksNo risks to the existing API is expecting. Following SemVer it's a minor API update, because it's an addition of the new API and not changing/deleting existing ones.
|
Your suggestion reminds me of a class named public bool MoveFirst(TKey key);
public bool MoveLast(TKey key);
public bool MoveBefore(TKey keyToMove, TKey mark);
public bool MoveAfter(TKey keyToMove, TKey mark);
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator(TKey startingElement);
public IEnumerator<KeyValuePair<TKey, TValue>> GetReverseEnumerator();
public IEnumerator<KeyValuePair<TKey, TValue>> GetReverseEnumerator(TKey startingElement); Do you think that these APIs go beyond the scope of your suggestion? |
Yes, the idea was to leave the original experience of using dictionary, but add defined enumeration order (insertion order) with minimal safe API (by analogy with HTTP safe methods: which are not alter the state). Remove first or last item of course are not safe but I consider them as kind of safe, because they do no change insertion order. Another point is to add this as a part of a framework, so noone needs to reinvent the wheel (as I did when I needed such data structure). I'd be happy if .NET has both Last but not least is a consistent with Java API (which is obviously richer) to make ramp-up to C# faster and smoother for Java guys/projects. But I am happy to discuss any critique/suggestions about proposed API. |
@danmoseley personally I've never encountered a situation where accessing the elements of a dictionary by index, like the experimental |
@theodorzoulias |
@alexzaitzev from the home page of Rock.Collections:
That dictionary has double-linked entries. Even with forward-only links, a ~10% slowdown should be expected. |
@theodorzoulias it was mentioned for |
For the record, the discussion about generic |
@svick that generic |
I recently had the need to implement an LRU cache and might have benefited from one of these. I think the issue with the cited .NET implementations in this discussion (inlcuding the non-generic That being said, I don't believe there is a strong need to ship a inbox solution for a few reasons:
|
@eiriktsarpalis thanks for your input, but we shouldn't be tied to just LRU cache example. Of course About thread safety: About using a |
Could you provide other use cases? Adding new collections is expensive, so it's unlikely we would add one for the sake of achieving parity with the Java APIs. We need to see data on use cases and there also needs to be substantial demand for the feature.
My point is that either choice comes with its own set of trade-offs that are important depending on the use case. I don't believe there can be a one-size-fits-all solution here. I could be wrong of course, if we do come up with one design that demonstrably suits 80% of use cases for .NET codebases then we might consider it for inclusion. |
Sure. Actually all use case where you need to maintain an order. For example, key-value pair often uses for storing configs, so having insertion order during config population/manipulation super handy. For printing let's say. Also it may be used in system development as a FIFO with a key access. Moreover I've seen some questions about it on SOF, so looks like people have some more examples. |
@eiriktsarpalis such a dictionary would have a behavior deviating from most, if not all, collections in the standard libraries, that allow multiple concurrent readers without synchronization. For example, from the documentation of the
A dictionary that maintains access order would be modified by just reading it. This might be an argument for not introducing a collection with this behavior in the standard .NET libraries. |
@theodorzoulias agreed, which is why I don't think we have a very compelling use case to incorporate such a type in System.Collections. |
@theodorzoulias actually I proposed to maintain an insertion order - not an access one. Java's @eiriktsarpalis what do you think? |
Like I said I don't believe we have compelling use cases to include this in System.Collections. I think this might be a better fit for a NuGet package. |
I have worked with a team that needed this in hundreds of places. What they actually did was to use normal Accessing by index wasn't needed, but order of enumeration was needed. Ordering wasn't done by any kind of key but by insertion order. This proposal would fit nicely. |
Where does this stand with #24826 done? |
Background and motivation
This data container is an analogue of LinkedHashMap implementation in Java, which is missing in C#. In the latest release was finally added PriorityQueue which was in Java since 2004. Bridging the gap between APIs of C# and Java will make possible quickly migrate the code from one language to another.
Dictionary with an opportunity to traversal the items in the addition order can be super useful in such scenarios as cache with LRU replacement policy or another cases where we want to maintain insertion order.
This is not a generic
OrderedDictionary
, because we don't want to alter the order, just traversal in the addition order and be able to read and delete the last/first items.API Proposal
API Usage
Alternative Designs
No response
Risks
No risks to the existing API is expecting. Following SemVer it's a minor API update, because it's an addition of the new API and not changing/deleting existing ones.
The text was updated successfully, but these errors were encountered: