dotnet / dotnet/runtime

[API Proposal]: `ICollectionDebugView<T>`

Open
#121,543 13 comments 1 reaction 3 assignees Claimed by @jcouv View on GitHub
api-needs-work area-System.Collections
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.