1
0
Fork 0
mirror of https://github.com/VSadov/Satori.git synced 2025-06-10 18:11:04 +09:00

Unify debug views of immutable dictionaries (#100745)

* Unify debug views of immutable dictionaries

Fixes #94289

- Updates the way the debugger displays the remaining dictionaries (Frozen, Immutable, ImmutableSorted, Concurrent) to present their keys and values in separate columns.
- Fixes debugger views of Builder classes of immutable collections. Previous custom implementations incorrectly treated the Builder classes as immutable.

* Fixed tests of debugger attributes with immutable and concurrent generic dictionaries

* Removed tests superseded by DebugView.Tests.
* Fixed DebugView.Tests of cuncurrent and immutable generic dictionaries which failed on .Net Framework

* Fix ns2.0 build.

---------

Co-authored-by: Eirik Tsarpalis <eirik.tsarpalis@gmail.com>
This commit is contained in:
Artur Zgodziński 2024-07-05 12:51:43 +01:00 committed by GitHub
parent 41d854fd44
commit 8bcbe18072
Signed by: github
GPG key ID: B5690EEEBB952194
14 changed files with 99 additions and 195 deletions

View file

@ -1,7 +1,10 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Concurrent;
using System.Collections.Frozen;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
@ -47,8 +50,8 @@ namespace System.Collections.Tests
new ("[\"Two\"]", "2"),
}
};
CustomKeyedCollection<string, int> collection = new ();
collection.GetKeyForItemHandler = value => (2 * value).ToString();
CustomKeyedCollection<string, int> collection = new();
collection.GetKeyForItemHandler = value => (2 * value).ToString();
collection.InsertItem(0, 1);
collection.InsertItem(1, 3);
yield return new object[] { collection,
@ -58,6 +61,53 @@ namespace System.Collections.Tests
new ("[\"6\"]", "3"),
}
};
yield return new object[] { new ConcurrentDictionary<int, string>(new KeyValuePair<int, string>[] { new(1, "One"), new(2, "Two") }),
new KeyValuePair<string, string>[]
{
new ("[1]", "\"One\""),
new ("[2]", "\"Two\""),
}
};
}
private static IEnumerable<object[]> TestDebuggerAttributes_AdditionalGenericDictionaries()
{
yield return new object[] { new Dictionary<int, string> { { 1, "One" }, { 2, "Two" } }.ToFrozenDictionary(),
new KeyValuePair<string, string>[]
{
new ("[1]", "\"One\""),
new ("[2]", "\"Two\""),
}
};
yield return new object[] { new Dictionary<int, string> { { 1, "One" }, { 2, "Two" } }.ToImmutableDictionary(),
new KeyValuePair<string, string>[]
{
new ("[1]", "\"One\""),
new ("[2]", "\"Two\""),
}
};
yield return new object[] { new Dictionary<int, string> { { 1, "One" }, { 2, "Two" } }.ToImmutableDictionary().ToBuilder(),
new KeyValuePair<string, string>[]
{
new ("[1]", "\"One\""),
new ("[2]", "\"Two\""),
}
};
yield return new object[] { new Dictionary<int, string> { { 1, "One" }, { 2, "Two" } }.ToImmutableSortedDictionary(),
new KeyValuePair<string, string>[]
{
new ("[1]", "\"One\""),
new ("[2]", "\"Two\""),
}
};
yield return new object[] { new Dictionary<int, string> { { 1, "One" }, { 2, "Two" } }.ToImmutableSortedDictionary().ToBuilder(),
new KeyValuePair<string, string>[]
{
new ("[1]", "\"One\""),
new ("[2]", "\"Two\""),
}
};
}
private static IEnumerable<object[]> TestDebuggerAttributes_NonGenericDictionaries()
@ -162,12 +212,14 @@ namespace System.Collections.Tests
public static IEnumerable<object[]> TestDebuggerAttributes_InputsPresentedAsDictionary()
{
var testCases = TestDebuggerAttributes_NonGenericDictionaries()
.Concat(TestDebuggerAttributes_AdditionalGenericDictionaries());
#if !NETFRAMEWORK
return TestDebuggerAttributes_NonGenericDictionaries()
return testCases
.Concat(TestDebuggerAttributes_GenericDictionaries());
#else
// In .Net Framework only non-generic dictionaries are displayed in a dictionary format by the debugger.
return TestDebuggerAttributes_NonGenericDictionaries();
// In .Net Framework, the generic dictionaries that are part of the framework are displayed in a list format by the debugger.
return testCases;
#endif
}

View file

@ -2,7 +2,6 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Reflection;
using System.Text;

View file

@ -21,6 +21,8 @@
Link="System\Collections\HashHelpers.cs" />
<Compile Include="$(CoreLibSharedDir)System\Collections\Concurrent\IProducerConsumerCollectionDebugView.cs"
Link="System\Collections\Concurrent\IProducerConsumerCollectionDebugView.cs" />
<Compile Include="$(CoreLibSharedDir)System\Collections\Generic\DebugViewDictionaryItem.cs"
Link="Common\System\Collections\Generic\DebugViewDictionaryItem.cs" />
</ItemGroup>
<ItemGroup>

View file

@ -2733,12 +2733,17 @@ namespace System.Collections.Concurrent
}
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
public KeyValuePair<TKey, TValue>[] Items
public DebugViewDictionaryItem<TKey, TValue>[] Items
{
get
{
var items = new KeyValuePair<TKey, TValue>[_dictionary.Count];
_dictionary.CopyTo(items, 0);
var keyValuePairs = new KeyValuePair<TKey, TValue>[_dictionary.Count];
_dictionary.CopyTo(keyValuePairs, 0);
var items = new DebugViewDictionaryItem<TKey, TValue>[keyValuePairs.Length];
for (int i = 0; i < items.Length; i++)
{
items[i] = new DebugViewDictionaryItem<TKey, TValue>(keyValuePairs[i]);
}
return items;
}
}

View file

@ -629,27 +629,6 @@ namespace System.Collections.Concurrent.Tests
Assert.Equal(2, dictionary.Count);
}
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsDebuggerTypeProxyAttributeSupported))]
public static void TestDebuggerAttributes()
{
DebuggerAttributes.ValidateDebuggerDisplayReferences(new ConcurrentDictionary<string, int>());
ConcurrentDictionary<string, int> dict = new ConcurrentDictionary<string, int>();
dict.TryAdd("One", 1);
dict.TryAdd("Two", 2);
DebuggerAttributeInfo info = DebuggerAttributes.ValidateDebuggerTypeProxyProperties(dict);
PropertyInfo itemProperty = info.Properties.Single(pr => pr.GetCustomAttribute<DebuggerBrowsableAttribute>().State == DebuggerBrowsableState.RootHidden);
KeyValuePair<string, int>[] items = itemProperty.GetValue(info.Instance) as KeyValuePair<string, int>[];
Assert.Equal(dict, items);
}
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsDebuggerTypeProxyAttributeSupported))]
public static void TestDebuggerAttributes_Null()
{
Type proxyType = DebuggerAttributes.GetProxyType(new ConcurrentDictionary<string, int>());
TargetInvocationException tie = Assert.Throws<TargetInvocationException>(() => Activator.CreateInstance(proxyType, (object)null));
Assert.IsType<ArgumentNullException>(tie.InnerException);
}
[Fact]
public static void TestNullComparer()
{

View file

@ -12,11 +12,11 @@ The System.Collections.Immutable library is built-in as part of the shared frame
<ItemGroup>
<Compile Include="Properties\InternalsVisibleTo.cs" />
<Compile Include="System\Polyfills.cs" />
<Compile Include="System\Collections\ThrowHelper.cs" />
<Compile Include="$(CoreLibSharedDir)System\Collections\HashHelpers.cs" Link="System\Collections\HashHelpers.cs" />
<Compile Include="$(CoreLibSharedDir)System\Collections\Generic\DebugViewDictionaryItem.cs" Link="Common\System\Collections\Generic\DebugViewDictionaryItem.cs" />
<Compile Include="$(CoreLibSharedDir)System\Collections\Generic\IDictionaryDebugView.cs" Link="Common\System\Collections\Generic\IDictionaryDebugView.cs" />
<Compile Include="System\Collections\Frozen\Constants.cs" />
<Compile Include="System\Collections\Frozen\DefaultFrozenDictionary.cs" />
<Compile Include="System\Collections\Frozen\DefaultFrozenSet.cs" />

View file

@ -27,7 +27,7 @@ namespace System.Collections.Immutable
/// </para>
/// </remarks>
[DebuggerDisplay("Count = {Count}")]
[DebuggerTypeProxy(typeof(ImmutableDictionaryBuilderDebuggerProxy<,>))]
[DebuggerTypeProxy(typeof(IDictionaryDebugView<,>))]
public sealed class Builder : IDictionary<TKey, TValue>, IReadOnlyDictionary<TKey, TValue>, IDictionary
{
/// <summary>
@ -709,36 +709,4 @@ namespace System.Collections.Immutable
}
}
}
/// <summary>
/// A simple view of the immutable collection that the debugger can show to the developer.
/// </summary>
internal sealed class ImmutableDictionaryBuilderDebuggerProxy<TKey, TValue> where TKey : notnull
{
/// <summary>
/// The collection to be enumerated.
/// </summary>
private readonly ImmutableDictionary<TKey, TValue>.Builder _map;
/// <summary>
/// The simple view of the collection.
/// </summary>
private KeyValuePair<TKey, TValue>[]? _contents;
/// <summary>
/// Initializes a new instance of the <see cref="ImmutableDictionaryBuilderDebuggerProxy{TKey, TValue}"/> class.
/// </summary>
/// <param name="map">The collection to display in the debugger</param>
public ImmutableDictionaryBuilderDebuggerProxy(ImmutableDictionary<TKey, TValue>.Builder map)
{
Requires.NotNull(map, nameof(map));
_map = map;
}
/// <summary>
/// Gets a simple debugger-viewable collection.
/// </summary>
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
public KeyValuePair<TKey, TValue>[] Contents => _contents ??= _map.ToArray(_map.Count);
}
}

View file

@ -12,16 +12,38 @@ namespace System.Collections.Immutable
/// </summary>
/// <typeparam name="TKey">The type of the dictionary's keys.</typeparam>
/// <typeparam name="TValue">The type of the dictionary's values.</typeparam>
internal sealed class ImmutableDictionaryDebuggerProxy<TKey, TValue> : ImmutableEnumerableDebuggerProxy<KeyValuePair<TKey, TValue>> where TKey : notnull
/// <remarks>
/// This class should only be used with immutable dictionaries, since it
/// caches the dictionary into an array for display in the debugger.
/// </remarks>
internal sealed class ImmutableDictionaryDebuggerProxy<TKey, TValue> where TKey : notnull
{
/// <summary>
/// The dictionary to show to the debugger.
/// </summary>
private readonly IReadOnlyDictionary<TKey, TValue> _dictionary;
/// <summary>
/// The contents of the dictionary, cached into an array.
/// </summary>
private DebugViewDictionaryItem<TKey, TValue>[]? _cachedContents;
/// <summary>
/// Initializes a new instance of the <see cref="ImmutableDictionaryDebuggerProxy{TKey, TValue}"/> class.
/// </summary>
/// <param name="dictionary">The enumerable to show in the debugger.</param>
/// <param name="dictionary">The dictionary to show in the debugger.</param>
public ImmutableDictionaryDebuggerProxy(IReadOnlyDictionary<TKey, TValue> dictionary)
: base(enumerable: dictionary)
{
Requires.NotNull(dictionary, nameof(dictionary));
_dictionary = dictionary;
}
/// <summary>
/// Gets the contents of the dictionary for display in the debugger.
/// </summary>
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
public DebugViewDictionaryItem<TKey, TValue>[] Contents => _cachedContents
??= _dictionary.Select(kv => new DebugViewDictionaryItem<TKey, TValue>(kv)).ToArray(_dictionary.Count);
}
/// <summary>

View file

@ -25,7 +25,7 @@ namespace System.Collections.Immutable
/// </para>
/// </remarks>
[DebuggerDisplay("Count = {Count}")]
[DebuggerTypeProxy(typeof(ImmutableSortedDictionaryBuilderDebuggerProxy<,>))]
[DebuggerTypeProxy(typeof(IDictionaryDebugView<,>))]
public sealed class Builder : IDictionary<TKey, TValue>, IReadOnlyDictionary<TKey, TValue>, IDictionary
{
/// <summary>
@ -645,35 +645,4 @@ namespace System.Collections.Immutable
#endregion
}
}
/// <summary>
/// A simple view of the immutable collection that the debugger can show to the developer.
/// </summary>
internal sealed class ImmutableSortedDictionaryBuilderDebuggerProxy<TKey, TValue> where TKey : notnull
{
/// <summary>
/// The collection to be enumerated.
/// </summary>
private readonly ImmutableSortedDictionary<TKey, TValue>.Builder _map;
/// <summary>
/// The simple view of the collection.
/// </summary>
private KeyValuePair<TKey, TValue>[]? _contents;
/// <summary>
/// Initializes a new instance of the <see cref="ImmutableSortedDictionaryBuilderDebuggerProxy{TKey, TValue}"/> class.
/// </summary>
/// <param name="map">The collection to display in the debugger</param>
public ImmutableSortedDictionaryBuilderDebuggerProxy(ImmutableSortedDictionary<TKey, TValue>.Builder map)
{
Requires.NotNull(map, nameof(map));
_map = map;
}
/// <summary>
/// Gets a simple debugger-viewable collection.
/// </summary>
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
public KeyValuePair<TKey, TValue>[] Contents => _contents ??= _map.ToArray(_map.Count);
}
}

View file

@ -255,27 +255,6 @@ namespace System.Collections.Immutable.Tests
Assert.Equal(5, populated.GetValueOrDefault("a", 1));
}
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsDebuggerTypeProxyAttributeSupported))]
public void DebuggerAttributesValid()
{
DebuggerAttributes.ValidateDebuggerDisplayReferences(ImmutableDictionary.CreateBuilder<string, int>());
ImmutableDictionary<int, string>.Builder builder = ImmutableDictionary.CreateBuilder<int, string>();
builder.Add(1, "One");
builder.Add(2, "Two");
DebuggerAttributeInfo info = DebuggerAttributes.ValidateDebuggerTypeProxyProperties(builder);
PropertyInfo itemProperty = info.Properties.Single(pr => pr.GetCustomAttribute<DebuggerBrowsableAttribute>().State == DebuggerBrowsableState.RootHidden);
KeyValuePair<int, string>[] items = itemProperty.GetValue(info.Instance) as KeyValuePair<int, string>[];
Assert.Equal(builder, items);
}
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsDebuggerTypeProxyAttributeSupported))]
public static void TestDebuggerAttributes_Null()
{
Type proxyType = DebuggerAttributes.GetProxyType(ImmutableHashSet.Create<string>());
TargetInvocationException tie = Assert.Throws<TargetInvocationException>(() => Activator.CreateInstance(proxyType, (object)null));
Assert.IsType<ArgumentNullException>(tie.InnerException);
}
[Fact]
public void ToImmutableDictionary()
{

View file

@ -349,28 +349,6 @@ namespace System.Collections.Immutable.Tests
enumerator.Dispose();
}
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsDebuggerTypeProxyAttributeSupported))]
public void DebuggerAttributesValid()
{
DebuggerAttributes.ValidateDebuggerDisplayReferences(ImmutableDictionary.Create<int, int>());
ImmutableDictionary<string, int> dict = ImmutableDictionary.Create<string, int>().Add("One", 1).Add("Two", 2);
DebuggerAttributeInfo info = DebuggerAttributes.ValidateDebuggerTypeProxyProperties(dict);
object rootNode = DebuggerAttributes.GetFieldValue(ImmutableDictionary.Create<string, string>(), "_root");
DebuggerAttributes.ValidateDebuggerDisplayReferences(rootNode);
PropertyInfo itemProperty = info.Properties.Single(pr => pr.GetCustomAttribute<DebuggerBrowsableAttribute>().State == DebuggerBrowsableState.RootHidden);
KeyValuePair<string, int>[] items = itemProperty.GetValue(info.Instance) as KeyValuePair<string, int>[];
Assert.Equal(dict, items);
}
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsDebuggerTypeProxyAttributeSupported))]
public static void TestDebuggerAttributes_Null()
{
Type proxyType = DebuggerAttributes.GetProxyType(ImmutableHashSet.Create<string>());
TargetInvocationException tie = Assert.Throws<TargetInvocationException>(() => Activator.CreateInstance(proxyType, (object)null));
Assert.IsType<ArgumentNullException>(tie.InnerException);
}
[Fact]
public void Clear_NoComparer_ReturnsEmptyWithoutComparer()
{

View file

@ -255,27 +255,6 @@ namespace System.Collections.Immutable.Tests
Assert.Equal(5, populated.GetValueOrDefault("a", 1));
}
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsDebuggerTypeProxyAttributeSupported))]
public void DebuggerAttributesValid()
{
DebuggerAttributes.ValidateDebuggerDisplayReferences(ImmutableSortedDictionary.CreateBuilder<string, int>());
ImmutableSortedDictionary<int, string>.Builder builder = ImmutableSortedDictionary.CreateBuilder<int, string>();
builder.Add(1, "One");
builder.Add(2, "Two");
DebuggerAttributeInfo info = DebuggerAttributes.ValidateDebuggerTypeProxyProperties(builder);
PropertyInfo itemProperty = info.Properties.Single(pr => pr.GetCustomAttribute<DebuggerBrowsableAttribute>().State == DebuggerBrowsableState.RootHidden);
KeyValuePair<int, string>[] items = itemProperty.GetValue(info.Instance) as KeyValuePair<int, string>[];
Assert.Equal(builder, items);
}
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsDebuggerTypeProxyAttributeSupported))]
public static void TestDebuggerAttributes_Null()
{
Type proxyType = DebuggerAttributes.GetProxyType(ImmutableSortedDictionary.CreateBuilder<int, string>());
TargetInvocationException tie = Assert.Throws<TargetInvocationException>(() => Activator.CreateInstance(proxyType, (object)null));
Assert.IsType<ArgumentNullException>(tie.InnerException);
}
[Fact]
public void ValueRef()
{

View file

@ -402,28 +402,6 @@ namespace System.Collections.Immutable.Tests
Assert.Equal(0, dictionary.Remove(2).Count);
}
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsDebuggerTypeProxyAttributeSupported))]
public void DebuggerAttributesValid()
{
DebuggerAttributes.ValidateDebuggerDisplayReferences(ImmutableSortedDictionary.Create<string, int>());
ImmutableSortedDictionary<int, int> dict = ImmutableSortedDictionary.Create<int, int>().Add(1, 2).Add(2, 3).Add(3, 4);
DebuggerAttributeInfo info = DebuggerAttributes.ValidateDebuggerTypeProxyProperties(dict);
object rootNode = DebuggerAttributes.GetFieldValue(ImmutableSortedDictionary.Create<string, string>(), "_root");
DebuggerAttributes.ValidateDebuggerDisplayReferences(rootNode);
PropertyInfo itemProperty = info.Properties.Single(pr => pr.GetCustomAttribute<DebuggerBrowsableAttribute>().State == DebuggerBrowsableState.RootHidden);
KeyValuePair<int, int>[] items = itemProperty.GetValue(info.Instance) as KeyValuePair<int, int>[];
Assert.Equal(dict, items);
}
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsDebuggerTypeProxyAttributeSupported))]
public static void TestDebuggerAttributes_Null()
{
Type proxyType = DebuggerAttributes.GetProxyType(ImmutableSortedDictionary.Create<int, int>());
TargetInvocationException tie = Assert.Throws<TargetInvocationException>(() => Activator.CreateInstance(proxyType, (object)null));
Assert.IsType<ArgumentNullException>(tie.InnerException);
}
[Fact]
public void ValueRef()
{

View file

@ -11,9 +11,7 @@ namespace System.Collections.Generic
public IDictionaryDebugView(IDictionary<TKey, TValue> dictionary)
{
ArgumentNullException.ThrowIfNull(dictionary);
_dict = dictionary;
_dict = dictionary ?? throw new ArgumentNullException(nameof(dictionary));
}
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
@ -39,9 +37,7 @@ namespace System.Collections.Generic
public DictionaryKeyCollectionDebugView(ICollection<TKey> collection)
{
ArgumentNullException.ThrowIfNull(collection);
_collection = collection;
_collection = collection ?? throw new ArgumentNullException(nameof(collection));
}
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
@ -62,9 +58,7 @@ namespace System.Collections.Generic
public DictionaryValueCollectionDebugView(ICollection<TValue> collection)
{
ArgumentNullException.ThrowIfNull(collection);
_collection = collection;
_collection = collection ?? throw new ArgumentNullException(nameof(collection));
}
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]