[API Proposal]: `ICollectionDebugView<T>`
- Dominant language
- C#
- Stars
- 18.3k
- Forks
- 5.6k
- PR merge metrics
- PR metrics pending
Description
### Background and motivation
I'm looking at a roslyn issue: https://github.com/dotnet/roslyn/issues/78651
When using collection expressions with a concrete type like ReadOnlyCollection, the debugger view is nice/useful.
But when using an interface type, the compiler synthesizes a collection type and the debugger view suffers.
```
static IReadOnlyCollection GetNames1() => ["Alice", "Bob", "Charlie"]; // bad experience
static IReadOnlyCollection GetNames2() => new ReadOnlyCollection(["Alice", "Bob", "Charlie"]); // good experience
```
A proposal would be to add `DebuggerDisplay` and `DebuggerTypeProxy` attributes on the synthesized collection types (to mirror [ReadOnlyCollection](https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/Collections/ObjectModel/ReadOnlyCollection.cs#L15)).
But the [proxy type](https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/ICollectionDebugView.cs#L8) we'd want to use is currently internal to the BCL.
FWIW, that type is [used](https://github.com/search?q=repo%3Adotnet%2Fruntime%20ICollectionDebugView&type=code) on a number of BCL collections.
I'm proposing we make that type public.
Some questions:
- is `System.Runtime.CompilerServices` the right namespace?
- should we use a name without `I` prefix (which suggests an interface)
### API Proposal
```csharp
namespace System.Runtime.CompilerServices;
public sealed class ICollectionDebugView
{
public ICollectionDebugView(ICollection collection);
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
public T[] Items { get; }
}
```
We'd also add corresponding debug types for dictionaries:
```csharp
namespace System.Runtime.CompilerServices;
internal sealed class IDictionaryDebugView where TKey : notnull
{
public IDictionaryDebugView(IDictionary dictionary);
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
public DebugViewDictionaryItem[] Items { get; }
}
[DebuggerDisplay("{Value}", Name = "[{Key}]")]
public readonly struct DebugViewDictionaryItem
{
public DebugViewDictionaryItem(TKey key, TValue value);
public DebugViewDictionaryItem(KeyValuePair keyValue);
[DebuggerBrowsable(DebuggerBrowsableState.Collapsed)]
public TKey Key { get; }
[DebuggerBrowsable(DebuggerBrowsableState.Collapsed)]
public TValue Value { get; }
}
public sealed class DictionaryKeyCollectionDebugView
{
public DictionaryKeyCollectionDebugView(ICollection collection);
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
public TKey[] Items { get; }
}
public sealed class DictionaryValueCollectionDebugView
{
public DictionaryValueCollectionDebugView(ICollection collection);
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
public TValue[] Items { get; }
}
```
### API Usage
`[DebuggerTypeProxy(typeof(ICollectionDebugView<>))]` would be used on the type synthesized by the C# compiler for a `IReadOnlyCollection` collection expression.
`IDictionaryDebugView` would be used on the type synthesized by the C# compiler for a `IReadOnlyDictionary` dictionary expression, and `DictionaryKeyCollectionDebugView` and `DictionaryValueCollectionDebugView` would be used on the types synthesized for the `.Keys` and `.Values`.
### Alternative Designs
_No response_
### Risks
_No response_
Contributor guide
Assessment
This issue has not been assessed yet.