From e7963bcfda7029e417538376d85535e15b2daa5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Artur=20Zgodzi=C5=84ski?= Date: Sat, 23 Sep 2023 15:25:01 +0100 Subject: [PATCH 1/7] Improves and unifies debug views of dictionaries. The change alows generic and non-generic dictionaries to be displayed in the same way: with separate columns for keys and values with an ability to expand each column. Fixes #88736 --- .../src/System.Collections.NonGeneric.csproj | 4 +-- .../src/System/Collections/SortedList.cs | 15 +++++---- .../src/System.Collections.csproj | 2 ++ .../System.Private.CoreLib.Shared.projitems | 2 +- .../Generic/DebugViewDictionaryItem.cs | 32 +++++++++++++++++++ .../Generic/IDictionaryDebugView.cs | 11 +++++-- .../src/System/Collections/Hashtable.cs | 15 ++++----- .../src/System/Collections/KeyValuePairs.cs | 26 --------------- .../Collections/ListDictionaryInternal.cs | 7 ++-- 9 files changed, 63 insertions(+), 51 deletions(-) create mode 100644 src/libraries/System.Private.CoreLib/src/System/Collections/Generic/DebugViewDictionaryItem.cs delete mode 100644 src/libraries/System.Private.CoreLib/src/System/Collections/KeyValuePairs.cs diff --git a/src/libraries/System.Collections.NonGeneric/src/System.Collections.NonGeneric.csproj b/src/libraries/System.Collections.NonGeneric/src/System.Collections.NonGeneric.csproj index 5fe02fe3551b12..7844617737cafd 100644 --- a/src/libraries/System.Collections.NonGeneric/src/System.Collections.NonGeneric.csproj +++ b/src/libraries/System.Collections.NonGeneric/src/System.Collections.NonGeneric.csproj @@ -13,8 +13,8 @@ - + diff --git a/src/libraries/System.Collections.NonGeneric/src/System/Collections/SortedList.cs b/src/libraries/System.Collections.NonGeneric/src/System/Collections/SortedList.cs index 720c1fd7a83869..df3ea3e0364256 100644 --- a/src/libraries/System.Collections.NonGeneric/src/System/Collections/SortedList.cs +++ b/src/libraries/System.Collections.NonGeneric/src/System/Collections/SortedList.cs @@ -11,6 +11,7 @@ ** ===========================================================*/ +using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; @@ -351,12 +352,12 @@ public virtual void CopyTo(Array array, int arrayIndex) // KeyValuePairs is different from Dictionary Entry in that it has special // debugger attributes on its fields. - internal virtual KeyValuePairs[] ToKeyValuePairsArray() + internal virtual DebugViewDictionaryItem[] ToDebugViewDictionaryItemArray() { - KeyValuePairs[] array = new KeyValuePairs[Count]; + var array = new DebugViewDictionaryItem[Count]; for (int i = 0; i < Count; i++) { - array[i] = new KeyValuePairs(keys[i], values[i]); + array[i] = new DebugViewDictionaryItem(keys[i], values[i]); } return array; } @@ -766,9 +767,9 @@ public override void SetByIndex(int index, object? value) } } - internal override KeyValuePairs[] ToKeyValuePairsArray() + internal override DebugViewDictionaryItem[] ToDebugViewDictionaryItemArray() { - return _list.ToKeyValuePairsArray(); + return _list.ToDebugViewDictionaryItemArray(); } public override void TrimToSize() @@ -1097,11 +1098,11 @@ public SortedListDebugView(SortedList sortedList) } [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] - public KeyValuePairs[] Items + public DebugViewDictionaryItem[] Items { get { - return _sortedList.ToKeyValuePairsArray(); + return _sortedList.ToDebugViewDictionaryItemArray(); } } } diff --git a/src/libraries/System.Collections/src/System.Collections.csproj b/src/libraries/System.Collections/src/System.Collections.csproj index a7e8976d48bfcd..17b09933523e8e 100644 --- a/src/libraries/System.Collections/src/System.Collections.csproj +++ b/src/libraries/System.Collections/src/System.Collections.csproj @@ -7,6 +7,8 @@ + + @@ -225,7 +226,6 @@ - diff --git a/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/DebugViewDictionaryItem.cs b/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/DebugViewDictionaryItem.cs new file mode 100644 index 00000000000000..b528f79d792282 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/DebugViewDictionaryItem.cs @@ -0,0 +1,32 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; + +namespace System.Collections.Generic +{ + /// + /// Defines a key/value pair for displaying an item of a dictionary by a debugger. + /// + [DebuggerDisplay("{Value}", Name = "[{Key}]")] + internal readonly struct DebugViewDictionaryItem + { + public DebugViewDictionaryItem(K key, V value) + { + Key = key; + Value = value; + } + + public DebugViewDictionaryItem(KeyValuePair keyValue) + { + Key = keyValue.Key; + Value = keyValue.Value; + } + + [DebuggerBrowsable(DebuggerBrowsableState.Collapsed)] + public K Key { get; init; } + + [DebuggerBrowsable(DebuggerBrowsableState.Collapsed)] + public V Value { get; init; } + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/IDictionaryDebugView.cs b/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/IDictionaryDebugView.cs index 97e32ad5da7df5..66e51a50811ecc 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/IDictionaryDebugView.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/IDictionaryDebugView.cs @@ -17,12 +17,17 @@ public IDictionaryDebugView(IDictionary dictionary) } [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] - public KeyValuePair[] Items + public DebugViewDictionaryItem[] Items { get { - KeyValuePair[] items = new KeyValuePair[_dict.Count]; - _dict.CopyTo(items, 0); + var keyValuePairs = new KeyValuePair[_dict.Count]; + _dict.CopyTo(keyValuePairs, 0); + var items = new DebugViewDictionaryItem[keyValuePairs.Length]; + for (int i = 0; i < items.Length; i++) + { + items[i] = new DebugViewDictionaryItem(keyValuePairs[i]); + } return items; } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Collections/Hashtable.cs b/src/libraries/System.Private.CoreLib/src/System/Collections/Hashtable.cs index a46b52c7334110..a154a499271eb7 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Collections/Hashtable.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Collections/Hashtable.cs @@ -11,6 +11,7 @@ ** ===========================================================*/ +using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Runtime.CompilerServices; @@ -581,13 +582,9 @@ public virtual void CopyTo(Array array, int arrayIndex) CopyEntries(array, arrayIndex); } - // Copies the values in this Hashtable to an KeyValuePairs array. - // KeyValuePairs is different from Dictionary Entry in that it has special - // debugger attributes on its fields. - - internal virtual KeyValuePairs[] ToKeyValuePairsArray() + internal virtual DebugViewDictionaryItem[] ToKeyValuePairsArray() { - KeyValuePairs[] array = new KeyValuePairs[_count]; + var array = new DebugViewDictionaryItem[_count]; int index = 0; Bucket[] lbuckets = _buckets; for (int i = lbuckets.Length; --i >= 0;) @@ -595,7 +592,7 @@ internal virtual KeyValuePairs[] ToKeyValuePairsArray() object? keyv = lbuckets[i].key; if ((keyv != null) && (keyv != _buckets)) { - array[index++] = new KeyValuePairs(keyv, lbuckets[i].val); + array[index++] = new DebugViewDictionaryItem(keyv, lbuckets[i].val); } } @@ -1385,7 +1382,7 @@ public override void OnDeserialization(object? sender) // call OnDeserialization on our parent table. } - internal override KeyValuePairs[] ToKeyValuePairsArray() + internal override DebugViewDictionaryItem[] ToKeyValuePairsArray() { return _table.ToKeyValuePairsArray(); } @@ -1509,7 +1506,7 @@ public HashtableDebugView(Hashtable hashtable) } [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] - public KeyValuePairs[] Items => _hashtable.ToKeyValuePairsArray(); + public DebugViewDictionaryItem[] Items => _hashtable.ToKeyValuePairsArray(); } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Collections/KeyValuePairs.cs b/src/libraries/System.Private.CoreLib/src/System/Collections/KeyValuePairs.cs deleted file mode 100644 index 87543d938751b1..00000000000000 --- a/src/libraries/System.Private.CoreLib/src/System/Collections/KeyValuePairs.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Diagnostics; - -namespace System.Collections -{ - /// - /// Defines key/value pairs for displaying items in a collection class under the debugger. - /// - [DebuggerDisplay("{_value}", Name = "[{_key}]")] - internal sealed class KeyValuePairs - { - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private readonly object _key; - - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private readonly object? _value; - - public KeyValuePairs(object key, object? value) - { - _value = value; - _key = key; - } - } -} diff --git a/src/libraries/System.Private.CoreLib/src/System/Collections/ListDictionaryInternal.cs b/src/libraries/System.Private.CoreLib/src/System/Collections/ListDictionaryInternal.cs index 730356461254dd..3dc89a50ea9716 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Collections/ListDictionaryInternal.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Collections/ListDictionaryInternal.cs @@ -1,5 +1,6 @@ // 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.Generic; using System.Diagnostics; using System.Runtime.CompilerServices; @@ -418,15 +419,15 @@ public ListDictionaryInternalDebugView(ListDictionaryInternal list) } [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] - public KeyValuePairs[] Items + public DebugViewDictionaryItem[] Items { get { - var array = new KeyValuePairs[_list.count]; + var array = new DebugViewDictionaryItem[_list.count]; int index = 0; for (DictionaryNode? node = _list.head; node != null; node = node.next) { - array[index++] = new KeyValuePairs(node.key, node.value); + array[index++] = new DebugViewDictionaryItem(node.key, node.value); } return array; } From a353c18c07898765b5a0fec8524a85961485ac42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Artur=20Zgodzi=C5=84ski?= Date: Wed, 25 Oct 2023 17:37:54 +0100 Subject: [PATCH 2/7] Fix the DebuggerView tests of dictionaries Included non-generic dictionaries in tests --- .../System/Collections/DebugView.Tests.cs | 99 +++++++++++++++---- .../System/Diagnostics/DebuggerAttributes.cs | 55 +++++++++-- 2 files changed, 128 insertions(+), 26 deletions(-) diff --git a/src/libraries/Common/tests/System/Collections/DebugView.Tests.cs b/src/libraries/Common/tests/System/Collections/DebugView.Tests.cs index d363af3479c3bb..84174f856e0ef0 100644 --- a/src/libraries/Common/tests/System/Collections/DebugView.Tests.cs +++ b/src/libraries/Common/tests/System/Collections/DebugView.Tests.cs @@ -11,14 +11,56 @@ namespace System.Collections.Tests { public class DebugView_Tests { - public static IEnumerable TestDebuggerAttributes_Inputs() + public static IEnumerable TestDebuggerAttributes_DictionaryInputs() + { + yield return new object[] { new Dictionary(), new KeyValuePair[0] }; + yield return new object[] { new SortedDictionary(), new KeyValuePair[0] }; + yield return new object[] { new Hashtable(), new KeyValuePair[0] }; + yield return new object[] { new SortedList(), new KeyValuePair[0] }; + yield return new object[] { new Exception().Data, new KeyValuePair[0] }; + + yield return new object[] { new Dictionary{{1, "One"}, {2, "Two"}}, + new KeyValuePair[] + { + KeyValuePair.Create("[1]", "\"One\""), + KeyValuePair.Create("[2]", "\"Two\""), + } + }; + yield return new object[] { new SortedDictionary{{"One", 1}, {"Two", 2}} , + new KeyValuePair[] + { + KeyValuePair.Create("[\"One\"]", "1"), + KeyValuePair.Create("[\"Two\"]", "2"), + } + }; + yield return new object[] { new Hashtable { { "a", 1 }, { "b", "B" } }, + new KeyValuePair[] + { + KeyValuePair.Create("[\"a\"]", "1"), + KeyValuePair.Create("[\"b\"]", "\"B\""), + } + }; + yield return new object[] { new SortedList { { "a", 1 }, { "b", "B" } }, + new KeyValuePair[] + { + KeyValuePair.Create("[\"a\"]", "1"), + KeyValuePair.Create("[\"b\"]", "\"B\""), + } + }; + yield return new object[] { new Exception { Data = { { "a", 1 }, { "b", "B" } } }.Data, + new KeyValuePair[] + { + KeyValuePair.Create("[\"a\"]", "1"), + KeyValuePair.Create("[\"b\"]", "\"B\""), + } + }; + } + + public static IEnumerable TestDebuggerAttributes_ListInputs() { - yield return new object[] { new Dictionary() }; - yield return new object[] { new HashSet() }; yield return new object[] { new LinkedList() }; yield return new object[] { new List() }; yield return new object[] { new Queue() }; - yield return new object[] { new SortedDictionary() }; yield return new object[] { new SortedList() }; yield return new object[] { new SortedSet() }; yield return new object[] { new Stack() }; @@ -29,40 +71,61 @@ public static IEnumerable TestDebuggerAttributes_Inputs() yield return new object[] { new SortedDictionary().Values }; yield return new object[] { new SortedList().Keys }; yield return new object[] { new SortedList().Values }; + yield return new object[] { new SortedList() }; - yield return new object[] { new Dictionary{{1, "One"}, {2, "Two"}} }; - yield return new object[] { new HashSet{"One", "Two"} }; + yield return new object[] { new HashSet { "One", "Two" } }; LinkedList linkedList = new LinkedList(); linkedList.AddFirst(1); linkedList.AddLast(2); yield return new object[] { linkedList }; - yield return new object[] { new List{1, 2} }; + yield return new object[] { new List { 1, 2 } }; Queue queue = new Queue(); queue.Enqueue(1); queue.Enqueue(2); yield return new object[] { queue }; - yield return new object[] { new SortedDictionary{{"One", 1}, {"Two", 2}} }; - yield return new object[] { new SortedList{{1, "One"}, {2, "Two"}} }; - yield return new object[] { new SortedSet{1, 2} }; + yield return new object[] { new SortedSet { 1, 2 } }; var stack = new Stack(); stack.Push(1); stack.Push(2); yield return new object[] { stack }; - yield return new object[] { new Dictionary{{1.0, 1.0f}, {2.0, 2.0f}}.Keys }; - yield return new object[] { new Dictionary{{1.0f, 1.0}, {2.0f, 2.0}}.Values }; - yield return new object[] { new SortedDictionary{{Guid.NewGuid(), "One"}, {Guid.NewGuid(), "Two"}}.Keys }; - yield return new object[] { new SortedDictionary{{1L, Guid.NewGuid()}, {2L, Guid.NewGuid()}}.Values }; - yield return new object[] { new SortedList{{"One", 1}, {"Two", 2}}.Keys }; - yield return new object[] { new SortedList{{1f, 1L}, {2f, 2L}}.Values }; + yield return new object[] { new Dictionary { { 1.0, 1.0f }, { 2.0, 2.0f } }.Keys }; + yield return new object[] { new Dictionary { { 1.0f, 1.0 }, { 2.0f, 2.0 } }.Values }; + yield return new object[] { new SortedDictionary { { Guid.NewGuid(), "One" }, { Guid.NewGuid(), "Two" } }.Keys }; + yield return new object[] { new SortedDictionary { { 1L, Guid.NewGuid() }, { 2L, Guid.NewGuid() } }.Values }; + yield return new object[] { new SortedList { { "One", 1 }, { "Two", 2 } }.Keys }; + yield return new object[] { new SortedList { { 1f, 1L }, { 2f, 2L } }.Values }; + } + + public static IEnumerable TestDebuggerAttributes_Inputs() + { + return TestDebuggerAttributes_DictionaryInputs() + .Select(t => new[] { t[0] }) + .Concat(TestDebuggerAttributes_ListInputs()); } [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsDebuggerTypeProxyAttributeSupported))] - [MemberData(nameof(TestDebuggerAttributes_Inputs))] - public static void TestDebuggerAttributes(object obj) + [MemberData(nameof(TestDebuggerAttributes_DictionaryInputs))] + public static void TestDebuggerAttributes_Dictionary(IDictionary obj, KeyValuePair[] expected) + { + DebuggerAttributes.ValidateDebuggerDisplayReferences(obj); + DebuggerAttributeInfo info = DebuggerAttributes.ValidateDebuggerTypeProxyProperties(obj); + PropertyInfo itemProperty = info.Properties.Single(pr => pr.GetCustomAttribute().State == DebuggerBrowsableState.RootHidden); + var itemArray = itemProperty.GetValue(info.Instance) as Array; + var formatted = itemArray.Cast() + .Select(DebuggerAttributes.ValidateFullyDebuggerDisplayReferences) + .Select(formattedResult => KeyValuePair.Create(formattedResult.Key, formattedResult.Value)) + .ToList(); + + CollectionAsserts.EqualUnordered((ICollection)expected, formatted); + } + + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsDebuggerTypeProxyAttributeSupported))] + [MemberData(nameof(TestDebuggerAttributes_ListInputs))] + public static void TestDebuggerAttributes_List(object obj) { DebuggerAttributes.ValidateDebuggerDisplayReferences(obj); DebuggerAttributeInfo info = DebuggerAttributes.ValidateDebuggerTypeProxyProperties(obj); diff --git a/src/libraries/Common/tests/System/Diagnostics/DebuggerAttributes.cs b/src/libraries/Common/tests/System/Diagnostics/DebuggerAttributes.cs index ee6853131e5d15..2c47fefa42ef96 100644 --- a/src/libraries/Common/tests/System/Diagnostics/DebuggerAttributes.cs +++ b/src/libraries/Common/tests/System/Diagnostics/DebuggerAttributes.cs @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; -using System.Diagnostics; +using System.Data; using System.Linq; using System.Reflection; using System.Text; @@ -15,6 +15,8 @@ internal class DebuggerAttributeInfo public IEnumerable Properties { get; set; } } + internal record DebuggerDisplayResult(string Value, string Key, string Type); + internal static class DebuggerAttributes { internal static object GetFieldValue(object obj, string fieldName) @@ -110,24 +112,61 @@ private static Type GetProxyType(Type type, Type[] genericTypeArguments) return proxyType; } - internal static string ValidateDebuggerDisplayReferences(object obj) + private static CustomAttributeData GetDebuggerDisplayAttribute(Type type) { - // Get the DebuggerDisplayAttribute for obj - Type objType = obj.GetType(); CustomAttributeData[] attrs = - objType.GetTypeInfo().CustomAttributes + type.GetTypeInfo().CustomAttributes .Where(a => a.AttributeType == typeof(DebuggerDisplayAttribute)) .ToArray(); if (attrs.Length != 1) { - throw new InvalidOperationException($"Expected one DebuggerDisplayAttribute on {objType}."); + throw new InvalidOperationException($"Expected one DebuggerDisplayAttribute on {type}."); } - CustomAttributeData cad = attrs[0]; + return attrs[0]; + } + + internal static DebuggerDisplayResult ValidateFullyDebuggerDisplayReferences(object obj) + { + CustomAttributeData cad = GetDebuggerDisplayAttribute(obj.GetType()); + + // Get the text of the DebuggerDisplayAttribute + string attrText = (string)cad.ConstructorArguments[0].Value; + string formattedValue = EvaluateDisplayString(attrText, obj); + + string formattedKey = FormatDebuggerDisplayNamedArgument(nameof(DebuggerDisplayAttribute.Name), cad, obj); + string formattedType = FormatDebuggerDisplayNamedArgument(nameof(DebuggerDisplayAttribute.Type), cad, obj); + + return new (Value: formattedValue, Key: formattedKey, Type: formattedType); + } + + private static string FormatDebuggerDisplayNamedArgument(string argumentName, CustomAttributeData debuggerDisplayAttributeData, object obj) + { + CustomAttributeNamedArgument namedAttribute = debuggerDisplayAttributeData.NamedArguments.FirstOrDefault(na => na.MemberName == argumentName); + if (namedAttribute != default) + { + var value = (string?)namedAttribute.TypedValue.Value; + if (!string.IsNullOrEmpty(value)) + { + return EvaluateDisplayString(value, obj); + } + } + return ""; + } + + internal static string ValidateDebuggerDisplayReferences(object obj) + { + CustomAttributeData cad = GetDebuggerDisplayAttribute(obj.GetType()); // Get the text of the DebuggerDisplayAttribute string attrText = (string)cad.ConstructorArguments[0].Value; - string[] segments = attrText.Split(new[] { '{', '}' }); + return EvaluateDisplayString(attrText, obj); + } + + private static string EvaluateDisplayString(string displayString, object obj) + { + Type objType = obj.GetType(); + string[] segments = displayString.Split(['{', '}']); if (segments.Length % 2 == 0) { From fc177d5fb41bccaefe8e18ebf0b55c31035607b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Artur=20Zgodzi=C5=84ski?= Date: Fri, 27 Oct 2023 18:46:45 +0100 Subject: [PATCH 3/7] Fix more DebuggerView tests * Included more types that implement IDictionary in the DebuggerView tests. * Improved the testing code to support classes with attributes declared by their base classes. * Fixed .Net Framework 4.8 build error by removing a dependency on the record c# feature. * Fixed tests remaining tests (outside of the System.Collections subset) --- .../System/Collections/DebugView.Tests.cs | 24 +++++ .../System/Diagnostics/DebuggerAttributes.cs | 96 ++++++++++--------- .../tests/SortedListTests.cs | 16 ++-- .../ReadOnlyDictionaryTests.cs | 4 +- 4 files changed, 84 insertions(+), 56 deletions(-) diff --git a/src/libraries/Common/tests/System/Collections/DebugView.Tests.cs b/src/libraries/Common/tests/System/Collections/DebugView.Tests.cs index 84174f856e0ef0..111edb07dbf4f3 100644 --- a/src/libraries/Common/tests/System/Collections/DebugView.Tests.cs +++ b/src/libraries/Common/tests/System/Collections/DebugView.Tests.cs @@ -14,9 +14,12 @@ public class DebugView_Tests public static IEnumerable TestDebuggerAttributes_DictionaryInputs() { yield return new object[] { new Dictionary(), new KeyValuePair[0] }; + yield return new object[] { new Dictionary().AsReadOnly(), new KeyValuePair[0] }; yield return new object[] { new SortedDictionary(), new KeyValuePair[0] }; yield return new object[] { new Hashtable(), new KeyValuePair[0] }; + yield return new object[] { Hashtable.Synchronized(new Hashtable()), new KeyValuePair[0] }; yield return new object[] { new SortedList(), new KeyValuePair[0] }; + yield return new object[] { SortedList.Synchronized(new SortedList()), new KeyValuePair[0] }; yield return new object[] { new Exception().Data, new KeyValuePair[0] }; yield return new object[] { new Dictionary{{1, "One"}, {2, "Two"}}, @@ -26,6 +29,13 @@ public static IEnumerable TestDebuggerAttributes_DictionaryInputs() KeyValuePair.Create("[2]", "\"Two\""), } }; + yield return new object[] { new Dictionary{{1, "One"}, {2, "Two"}}.AsReadOnly(), + new KeyValuePair[] + { + KeyValuePair.Create("[1]", "\"One\""), + KeyValuePair.Create("[2]", "\"Two\""), + } + }; yield return new object[] { new SortedDictionary{{"One", 1}, {"Two", 2}} , new KeyValuePair[] { @@ -40,6 +50,13 @@ public static IEnumerable TestDebuggerAttributes_DictionaryInputs() KeyValuePair.Create("[\"b\"]", "\"B\""), } }; + yield return new object[] { Hashtable.Synchronized(new Hashtable { { "a", 1 }, { "b", "B" } }), + new KeyValuePair[] + { + KeyValuePair.Create("[\"a\"]", "1"), + KeyValuePair.Create("[\"b\"]", "\"B\""), + } + }; yield return new object[] { new SortedList { { "a", 1 }, { "b", "B" } }, new KeyValuePair[] { @@ -47,6 +64,13 @@ public static IEnumerable TestDebuggerAttributes_DictionaryInputs() KeyValuePair.Create("[\"b\"]", "\"B\""), } }; + yield return new object[] { SortedList.Synchronized(new SortedList { { "a", 1 }, { "b", "B" } }), + new KeyValuePair[] + { + KeyValuePair.Create("[\"a\"]", "1"), + KeyValuePair.Create("[\"b\"]", "\"B\""), + } + }; yield return new object[] { new Exception { Data = { { "a", 1 }, { "b", "B" } } }.Data, new KeyValuePair[] { diff --git a/src/libraries/Common/tests/System/Diagnostics/DebuggerAttributes.cs b/src/libraries/Common/tests/System/Diagnostics/DebuggerAttributes.cs index 2c47fefa42ef96..472694d0e567d0 100644 --- a/src/libraries/Common/tests/System/Diagnostics/DebuggerAttributes.cs +++ b/src/libraries/Common/tests/System/Diagnostics/DebuggerAttributes.cs @@ -15,7 +15,12 @@ internal class DebuggerAttributeInfo public IEnumerable Properties { get; set; } } - internal record DebuggerDisplayResult(string Value, string Key, string Type); + internal class DebuggerDisplayResult + { + public string Value { get; set; } + public string Key { get; set; } + public string Type { get; set; } + } internal static class DebuggerAttributes { @@ -88,55 +93,62 @@ public static IEnumerable GetDebuggerVisibleProperties(Type debugg public static Type GetProxyType(Type type) => GetProxyType(type, type.GenericTypeArguments); - private static Type GetProxyType(Type type, Type[] genericTypeArguments) + internal static DebuggerDisplayResult ValidateFullyDebuggerDisplayReferences(object obj) { - // Get the DebuggerTypeProxyAttribute for obj - CustomAttributeData[] attrs = - type.GetTypeInfo().CustomAttributes - .Where(a => a.AttributeType == typeof(DebuggerTypeProxyAttribute)) - .ToArray(); - if (attrs.Length != 1) - { - throw new InvalidOperationException($"Expected one DebuggerTypeProxyAttribute on {type}."); - } - CustomAttributeData cad = attrs[0]; + CustomAttributeData cad = FindAttribute(obj.GetType(), attributeType: typeof(DebuggerDisplayAttribute)); - Type proxyType = cad.ConstructorArguments[0].ArgumentType == typeof(Type) ? - (Type)cad.ConstructorArguments[0].Value : - Type.GetType((string)cad.ConstructorArguments[0].Value); - if (genericTypeArguments.Length > 0) - { - proxyType = proxyType.MakeGenericType(genericTypeArguments); - } + // Get the text of the DebuggerDisplayAttribute + string attrText = (string)cad.ConstructorArguments[0].Value; + string formattedValue = EvaluateDisplayString(attrText, obj); - return proxyType; + string formattedKey = FormatDebuggerDisplayNamedArgument(nameof(DebuggerDisplayAttribute.Name), cad, obj); + string formattedType = FormatDebuggerDisplayNamedArgument(nameof(DebuggerDisplayAttribute.Type), cad, obj); + + return new DebuggerDisplayResult { Value = formattedValue, Key = formattedKey, Type = formattedType }; + } + + internal static string ValidateDebuggerDisplayReferences(object obj) + { + CustomAttributeData cad = FindAttribute(obj.GetType(), attributeType: typeof(DebuggerDisplayAttribute)); + + // Get the text of the DebuggerDisplayAttribute + string attrText = (string)cad.ConstructorArguments[0].Value; + + return EvaluateDisplayString(attrText, obj); } - private static CustomAttributeData GetDebuggerDisplayAttribute(Type type) + private static CustomAttributeData FindAttribute(Type type, Type attributeType) { - CustomAttributeData[] attrs = - type.GetTypeInfo().CustomAttributes - .Where(a => a.AttributeType == typeof(DebuggerDisplayAttribute)) - .ToArray(); - if (attrs.Length != 1) + for (var t = type; t != null; t = t.BaseType) { - throw new InvalidOperationException($"Expected one DebuggerDisplayAttribute on {type}."); + CustomAttributeData[] attributes = t.GetTypeInfo().CustomAttributes + .Where(a => a.AttributeType == attributeType) + .ToArray(); + if (attributes.Length != 0) + { + if (attributes.Length > 1) + { + throw new InvalidOperationException($"Expected one {attributeType.Name} on {type} but found more."); + } + return attributes[0]; + } } - return attrs[0]; + throw new InvalidOperationException($"Expected one {attributeType.Name} on {type}."); } - internal static DebuggerDisplayResult ValidateFullyDebuggerDisplayReferences(object obj) + private static Type GetProxyType(Type type, Type[] genericTypeArguments) { - CustomAttributeData cad = GetDebuggerDisplayAttribute(obj.GetType()); + CustomAttributeData cad = FindAttribute(type, attributeType: typeof(DebuggerTypeProxyAttribute)); - // Get the text of the DebuggerDisplayAttribute - string attrText = (string)cad.ConstructorArguments[0].Value; - string formattedValue = EvaluateDisplayString(attrText, obj); - - string formattedKey = FormatDebuggerDisplayNamedArgument(nameof(DebuggerDisplayAttribute.Name), cad, obj); - string formattedType = FormatDebuggerDisplayNamedArgument(nameof(DebuggerDisplayAttribute.Type), cad, obj); + Type proxyType = cad.ConstructorArguments[0].ArgumentType == typeof(Type) ? + (Type)cad.ConstructorArguments[0].Value : + Type.GetType((string)cad.ConstructorArguments[0].Value); + if (genericTypeArguments.Length > 0) + { + proxyType = proxyType.MakeGenericType(genericTypeArguments); + } - return new (Value: formattedValue, Key: formattedKey, Type: formattedType); + return proxyType; } private static string FormatDebuggerDisplayNamedArgument(string argumentName, CustomAttributeData debuggerDisplayAttributeData, object obj) @@ -153,16 +165,6 @@ private static string FormatDebuggerDisplayNamedArgument(string argumentName, Cu return ""; } - internal static string ValidateDebuggerDisplayReferences(object obj) - { - CustomAttributeData cad = GetDebuggerDisplayAttribute(obj.GetType()); - - // Get the text of the DebuggerDisplayAttribute - string attrText = (string)cad.ConstructorArguments[0].Value; - - return EvaluateDisplayString(attrText, obj); - } - private static string EvaluateDisplayString(string displayString, object obj) { Type objType = obj.GetType(); diff --git a/src/libraries/System.Collections.NonGeneric/tests/SortedListTests.cs b/src/libraries/System.Collections.NonGeneric/tests/SortedListTests.cs index 5c6d37db7db9e4..51ef75a58fa4ad 100644 --- a/src/libraries/System.Collections.NonGeneric/tests/SortedListTests.cs +++ b/src/libraries/System.Collections.NonGeneric/tests/SortedListTests.cs @@ -223,19 +223,21 @@ public void DebuggerAttribute_NormalList() { var list = new SortedList() { { "a", 1 }, { "b", 2 } }; DebuggerAttributeInfo debuggerAttribute = DebuggerAttributes.ValidateDebuggerTypeProxyProperties(list); - PropertyInfo infoProperty = debuggerAttribute.Properties.Single(property => property.Name == "Items"); - object[] items = (object[])infoProperty.GetValue(debuggerAttribute.Instance); - Assert.Equal(list.Count, items.Length); + PropertyInfo itemProperty = debuggerAttribute.Properties.Single(pr => pr.GetCustomAttribute().State == DebuggerBrowsableState.RootHidden); + var itemArray = itemProperty.GetValue(debuggerAttribute.Instance) as Array; + + Assert.Equal(list.Count, itemArray.Length); } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsDebuggerTypeProxyAttributeSupported))] public void DebuggerAttribute_SynchronizedList() { var list = SortedList.Synchronized(new SortedList() { { "a", 1 }, { "b", 2 } }); - DebuggerAttributeInfo debuggerAttribute = DebuggerAttributes.ValidateDebuggerTypeProxyProperties(typeof(SortedList), list); - PropertyInfo infoProperty = debuggerAttribute.Properties.Single(property => property.Name == "Items"); - object[] items = (object[])infoProperty.GetValue(debuggerAttribute.Instance); - Assert.Equal(list.Count, items.Length); + DebuggerAttributeInfo debuggerAttribute = DebuggerAttributes.ValidateDebuggerTypeProxyProperties(list); + PropertyInfo itemProperty = debuggerAttribute.Properties.Single(pr => pr.GetCustomAttribute().State == DebuggerBrowsableState.RootHidden); + var itemArray = itemProperty.GetValue(debuggerAttribute.Instance) as Array; + + Assert.Equal(list.Count, itemArray.Length); } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsDebuggerTypeProxyAttributeSupported))] diff --git a/src/libraries/System.ObjectModel/tests/ReadOnlyDictionary/ReadOnlyDictionaryTests.cs b/src/libraries/System.ObjectModel/tests/ReadOnlyDictionary/ReadOnlyDictionaryTests.cs index 5b6ee84081d106..bb9c565c61f166 100644 --- a/src/libraries/System.ObjectModel/tests/ReadOnlyDictionary/ReadOnlyDictionaryTests.cs +++ b/src/libraries/System.ObjectModel/tests/ReadOnlyDictionary/ReadOnlyDictionaryTests.cs @@ -239,8 +239,8 @@ public static void DebuggerAttributeTests() DebuggerAttributes.ValidateDebuggerDisplayReferences(dict); DebuggerAttributeInfo info = DebuggerAttributes.ValidateDebuggerTypeProxyProperties(dict); PropertyInfo itemProperty = info.Properties.Single(pr => pr.GetCustomAttribute().State == DebuggerBrowsableState.RootHidden); - KeyValuePair[] pairs = itemProperty.GetValue(info.Instance) as KeyValuePair[]; - Assert.Equal(dict, pairs); + var itemArray = itemProperty.GetValue(info.Instance) as Array; + Assert.Equal(dict.Count, itemArray.Length); DebuggerAttributes.ValidateDebuggerDisplayReferences(dict.Keys); info = DebuggerAttributes.ValidateDebuggerTypeProxyProperties(typeof(ReadOnlyDictionary.KeyCollection), new Type[] { typeof(int) }, dict.Keys); From c71a9bc425129acaa1c69d4b306ec5a166cdc756 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Artur=20Zgodzi=C5=84ski?= Date: Sun, 29 Oct 2023 13:10:42 +0000 Subject: [PATCH 4/7] Fix DebugView.Tests build errors on .Net Framework --- .../System/Collections/DebugView.Tests.cs | 39 ++++++++++--------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/src/libraries/Common/tests/System/Collections/DebugView.Tests.cs b/src/libraries/Common/tests/System/Collections/DebugView.Tests.cs index 111edb07dbf4f3..983a904a687217 100644 --- a/src/libraries/Common/tests/System/Collections/DebugView.Tests.cs +++ b/src/libraries/Common/tests/System/Collections/DebugView.Tests.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Diagnostics; using System.Linq; using System.Reflection; @@ -14,7 +15,7 @@ public class DebugView_Tests public static IEnumerable TestDebuggerAttributes_DictionaryInputs() { yield return new object[] { new Dictionary(), new KeyValuePair[0] }; - yield return new object[] { new Dictionary().AsReadOnly(), new KeyValuePair[0] }; + yield return new object[] { new ReadOnlyDictionary(new Dictionary()), new KeyValuePair[0] }; yield return new object[] { new SortedDictionary(), new KeyValuePair[0] }; yield return new object[] { new Hashtable(), new KeyValuePair[0] }; yield return new object[] { Hashtable.Synchronized(new Hashtable()), new KeyValuePair[0] }; @@ -25,57 +26,57 @@ public static IEnumerable TestDebuggerAttributes_DictionaryInputs() yield return new object[] { new Dictionary{{1, "One"}, {2, "Two"}}, new KeyValuePair[] { - KeyValuePair.Create("[1]", "\"One\""), - KeyValuePair.Create("[2]", "\"Two\""), + new ("[1]", "\"One\""), + new ("[2]", "\"Two\""), } }; - yield return new object[] { new Dictionary{{1, "One"}, {2, "Two"}}.AsReadOnly(), + yield return new object[] { new ReadOnlyDictionary(new Dictionary{{1, "One"}, {2, "Two"}}), new KeyValuePair[] { - KeyValuePair.Create("[1]", "\"One\""), - KeyValuePair.Create("[2]", "\"Two\""), + new ("[1]", "\"One\""), + new ("[2]", "\"Two\""), } }; yield return new object[] { new SortedDictionary{{"One", 1}, {"Two", 2}} , new KeyValuePair[] { - KeyValuePair.Create("[\"One\"]", "1"), - KeyValuePair.Create("[\"Two\"]", "2"), + new ("[\"One\"]", "1"), + new ("[\"Two\"]", "2"), } }; yield return new object[] { new Hashtable { { "a", 1 }, { "b", "B" } }, new KeyValuePair[] { - KeyValuePair.Create("[\"a\"]", "1"), - KeyValuePair.Create("[\"b\"]", "\"B\""), + new ("[\"a\"]", "1"), + new ("[\"b\"]", "\"B\""), } }; yield return new object[] { Hashtable.Synchronized(new Hashtable { { "a", 1 }, { "b", "B" } }), new KeyValuePair[] { - KeyValuePair.Create("[\"a\"]", "1"), - KeyValuePair.Create("[\"b\"]", "\"B\""), + new ("[\"a\"]", "1"), + new ("[\"b\"]", "\"B\""), } }; yield return new object[] { new SortedList { { "a", 1 }, { "b", "B" } }, new KeyValuePair[] { - KeyValuePair.Create("[\"a\"]", "1"), - KeyValuePair.Create("[\"b\"]", "\"B\""), + new ("[\"a\"]", "1"), + new ("[\"b\"]", "\"B\""), } }; yield return new object[] { SortedList.Synchronized(new SortedList { { "a", 1 }, { "b", "B" } }), new KeyValuePair[] { - KeyValuePair.Create("[\"a\"]", "1"), - KeyValuePair.Create("[\"b\"]", "\"B\""), + new ("[\"a\"]", "1"), + new ("[\"b\"]", "\"B\""), } }; yield return new object[] { new Exception { Data = { { "a", 1 }, { "b", "B" } } }.Data, new KeyValuePair[] { - KeyValuePair.Create("[\"a\"]", "1"), - KeyValuePair.Create("[\"b\"]", "\"B\""), + new ("[\"a\"]", "1"), + new ("[\"b\"]", "\"B\""), } }; } @@ -141,7 +142,7 @@ public static void TestDebuggerAttributes_Dictionary(IDictionary obj, KeyValuePa var itemArray = itemProperty.GetValue(info.Instance) as Array; var formatted = itemArray.Cast() .Select(DebuggerAttributes.ValidateFullyDebuggerDisplayReferences) - .Select(formattedResult => KeyValuePair.Create(formattedResult.Key, formattedResult.Value)) + .Select(formattedResult => new KeyValuePair(formattedResult.Key, formattedResult.Value)) .ToList(); CollectionAsserts.EqualUnordered((ICollection)expected, formatted); From 4c7782b6bd5b69d450b2818814a592fea2c500b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Artur=20Zgodzi=C5=84ski?= Date: Mon, 30 Oct 2023 11:27:46 +0000 Subject: [PATCH 5/7] Update DebugView tests to expect different outcomes on .Net Framework .Net Framwork does not support recent improvements in the way the debugger displays a dictionary. Depending on the framwork used, the debugger view of generic dictionaries and ListDictionaryInternal are different. --- .../System/Collections/DebugView.Tests.cs | 54 ++++++++++++++----- 1 file changed, 42 insertions(+), 12 deletions(-) diff --git a/src/libraries/Common/tests/System/Collections/DebugView.Tests.cs b/src/libraries/Common/tests/System/Collections/DebugView.Tests.cs index 983a904a687217..154db6d4821fec 100644 --- a/src/libraries/Common/tests/System/Collections/DebugView.Tests.cs +++ b/src/libraries/Common/tests/System/Collections/DebugView.Tests.cs @@ -12,16 +12,11 @@ namespace System.Collections.Tests { public class DebugView_Tests { - public static IEnumerable TestDebuggerAttributes_DictionaryInputs() + private static IEnumerable TestDebuggerAttributes_GenericDictionaries() { yield return new object[] { new Dictionary(), new KeyValuePair[0] }; - yield return new object[] { new ReadOnlyDictionary(new Dictionary()), new KeyValuePair[0] }; + yield return new object[] { new ReadOnlyDictionary(new Dictionary()), new KeyValuePair[0] }; yield return new object[] { new SortedDictionary(), new KeyValuePair[0] }; - yield return new object[] { new Hashtable(), new KeyValuePair[0] }; - yield return new object[] { Hashtable.Synchronized(new Hashtable()), new KeyValuePair[0] }; - yield return new object[] { new SortedList(), new KeyValuePair[0] }; - yield return new object[] { SortedList.Synchronized(new SortedList()), new KeyValuePair[0] }; - yield return new object[] { new Exception().Data, new KeyValuePair[0] }; yield return new object[] { new Dictionary{{1, "One"}, {2, "Two"}}, new KeyValuePair[] @@ -44,6 +39,15 @@ public static IEnumerable TestDebuggerAttributes_DictionaryInputs() new ("[\"Two\"]", "2"), } }; + } + + private static IEnumerable TestDebuggerAttributes_NonGenericDictionaries() + { + yield return new object[] { new Hashtable(), new KeyValuePair[0] }; + yield return new object[] { Hashtable.Synchronized(new Hashtable()), new KeyValuePair[0] }; + yield return new object[] { new SortedList(), new KeyValuePair[0] }; + yield return new object[] { SortedList.Synchronized(new SortedList()), new KeyValuePair[0] }; + yield return new object[] { new Hashtable { { "a", 1 }, { "b", "B" } }, new KeyValuePair[] { @@ -72,6 +76,8 @@ public static IEnumerable TestDebuggerAttributes_DictionaryInputs() new ("[\"b\"]", "\"B\""), } }; +#if !NETFRAMEWORK // ListDictionaryInternal in .Net Framework is not annotated with debugger attributes. + yield return new object[] { new Exception().Data, new KeyValuePair[0] }; yield return new object[] { new Exception { Data = { { "a", 1 }, { "b", "B" } } }.Data, new KeyValuePair[] { @@ -79,9 +85,10 @@ public static IEnumerable TestDebuggerAttributes_DictionaryInputs() new ("[\"b\"]", "\"B\""), } }; +#endif } - public static IEnumerable TestDebuggerAttributes_ListInputs() + private static IEnumerable TestDebuggerAttributes_ListInputs() { yield return new object[] { new LinkedList() }; yield return new object[] { new List() }; @@ -125,15 +132,38 @@ public static IEnumerable TestDebuggerAttributes_ListInputs() yield return new object[] { new SortedList { { 1f, 1L }, { 2f, 2L } }.Values }; } - public static IEnumerable TestDebuggerAttributes_Inputs() + public static IEnumerable TestDebuggerAttributes_InputsPresentedAsDictionary() + { +#if !NETFRAMEWORK + return TestDebuggerAttributes_NonGenericDictionaries() + .Concat(TestDebuggerAttributes_GenericDictionaries()); +#else + // In .Net Framework only non-generic dictionaries are displayed in a dictionary format by the debugger. + return TestDebuggerAttributes_NonGenericDictionaries(); +#endif + } + + public static IEnumerable TestDebuggerAttributes_InputsPresentedAsList() { - return TestDebuggerAttributes_DictionaryInputs() +#if !NETFRAMEWORK + return TestDebuggerAttributes_ListInputs(); +#else + // In .Net Framework generic dictionaries are displayed in a list format by the debugger. + return TestDebuggerAttributes_GenericDictionaries() .Select(t => new[] { t[0] }) .Concat(TestDebuggerAttributes_ListInputs()); +#endif + } + + public static IEnumerable TestDebuggerAttributes_Inputs() + { + return TestDebuggerAttributes_InputsPresentedAsDictionary() + .Select(t => new[] { t[0] }) + .Concat(TestDebuggerAttributes_InputsPresentedAsList()); } [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsDebuggerTypeProxyAttributeSupported))] - [MemberData(nameof(TestDebuggerAttributes_DictionaryInputs))] + [MemberData(nameof(TestDebuggerAttributes_InputsPresentedAsDictionary))] public static void TestDebuggerAttributes_Dictionary(IDictionary obj, KeyValuePair[] expected) { DebuggerAttributes.ValidateDebuggerDisplayReferences(obj); @@ -149,7 +179,7 @@ public static void TestDebuggerAttributes_Dictionary(IDictionary obj, KeyValuePa } [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsDebuggerTypeProxyAttributeSupported))] - [MemberData(nameof(TestDebuggerAttributes_ListInputs))] + [MemberData(nameof(TestDebuggerAttributes_InputsPresentedAsList))] public static void TestDebuggerAttributes_List(object obj) { DebuggerAttributes.ValidateDebuggerDisplayReferences(obj); From 70a5d5553ce44db7e767be1d3e6c9a90868883c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Artur=20Zgodzi=C5=84ski?= Date: Tue, 31 Oct 2023 20:36:24 +0000 Subject: [PATCH 6/7] Applied suggested changes and fixes * mostly code style changes * restored a rest for an empty HashSet. * fixed testing of the generic SortedList. --- .../System/Collections/DebugView.Tests.cs | 27 ++++++++++++------- .../System/Diagnostics/DebuggerAttributes.cs | 4 +-- .../tests/SortedListTests.cs | 4 +-- .../ReadOnlyDictionaryTests.cs | 2 +- .../Generic/DebugViewDictionaryItem.cs | 10 +++---- .../Generic/IDictionaryDebugView.cs | 14 +++++----- 6 files changed, 35 insertions(+), 26 deletions(-) diff --git a/src/libraries/Common/tests/System/Collections/DebugView.Tests.cs b/src/libraries/Common/tests/System/Collections/DebugView.Tests.cs index 154db6d4821fec..a67b7617ba681d 100644 --- a/src/libraries/Common/tests/System/Collections/DebugView.Tests.cs +++ b/src/libraries/Common/tests/System/Collections/DebugView.Tests.cs @@ -17,6 +17,7 @@ private static IEnumerable TestDebuggerAttributes_GenericDictionaries( yield return new object[] { new Dictionary(), new KeyValuePair[0] }; yield return new object[] { new ReadOnlyDictionary(new Dictionary()), new KeyValuePair[0] }; yield return new object[] { new SortedDictionary(), new KeyValuePair[0] }; + yield return new object[] { new SortedList(), new KeyValuePair[0] }; yield return new object[] { new Dictionary{{1, "One"}, {2, "Two"}}, new KeyValuePair[] @@ -39,6 +40,13 @@ private static IEnumerable TestDebuggerAttributes_GenericDictionaries( new ("[\"Two\"]", "2"), } }; + yield return new object[] { new SortedList { { "One", 1.0 }, { "Two", 2.0 } }, + new KeyValuePair[] + { + new ("[\"One\"]", "1"), + new ("[\"Two\"]", "2"), + } + }; } private static IEnumerable TestDebuggerAttributes_NonGenericDictionaries() @@ -90,6 +98,7 @@ private static IEnumerable TestDebuggerAttributes_NonGenericDictionari private static IEnumerable TestDebuggerAttributes_ListInputs() { + yield return new object[] { new HashSet() }; yield return new object[] { new LinkedList() }; yield return new object[] { new List() }; yield return new object[] { new Queue() }; @@ -103,33 +112,33 @@ private static IEnumerable TestDebuggerAttributes_ListInputs() yield return new object[] { new SortedDictionary().Values }; yield return new object[] { new SortedList().Keys }; yield return new object[] { new SortedList().Values }; - yield return new object[] { new SortedList() }; yield return new object[] { new HashSet { "One", "Two" } }; - LinkedList linkedList = new LinkedList(); + LinkedList linkedList = new(); linkedList.AddFirst(1); linkedList.AddLast(2); yield return new object[] { linkedList }; yield return new object[] { new List { 1, 2 } }; - Queue queue = new Queue(); + Queue queue = new(); queue.Enqueue(1); queue.Enqueue(2); yield return new object[] { queue }; yield return new object[] { new SortedSet { 1, 2 } }; - var stack = new Stack(); + Stack stack = new(); stack.Push(1); stack.Push(2); yield return new object[] { stack }; + yield return new object[] { new SortedList { { "One", 1 }, { "Two", 2 } }.Keys }; + yield return new object[] { new SortedList { { 1f, 1L }, { 2f, 2L } }.Values }; + yield return new object[] { new Dictionary { { 1.0, 1.0f }, { 2.0, 2.0f } }.Keys }; yield return new object[] { new Dictionary { { 1.0f, 1.0 }, { 2.0f, 2.0 } }.Values }; yield return new object[] { new SortedDictionary { { Guid.NewGuid(), "One" }, { Guid.NewGuid(), "Two" } }.Keys }; yield return new object[] { new SortedDictionary { { 1L, Guid.NewGuid() }, { 2L, Guid.NewGuid() } }.Values }; - yield return new object[] { new SortedList { { "One", 1 }, { "Two", 2 } }.Keys }; - yield return new object[] { new SortedList { { 1f, 1L }, { 2f, 2L } }.Values }; } public static IEnumerable TestDebuggerAttributes_InputsPresentedAsDictionary() @@ -142,7 +151,7 @@ public static IEnumerable TestDebuggerAttributes_InputsPresentedAsDict return TestDebuggerAttributes_NonGenericDictionaries(); #endif } - + public static IEnumerable TestDebuggerAttributes_InputsPresentedAsList() { #if !NETFRAMEWORK @@ -169,8 +178,8 @@ public static void TestDebuggerAttributes_Dictionary(IDictionary obj, KeyValuePa DebuggerAttributes.ValidateDebuggerDisplayReferences(obj); DebuggerAttributeInfo info = DebuggerAttributes.ValidateDebuggerTypeProxyProperties(obj); PropertyInfo itemProperty = info.Properties.Single(pr => pr.GetCustomAttribute().State == DebuggerBrowsableState.RootHidden); - var itemArray = itemProperty.GetValue(info.Instance) as Array; - var formatted = itemArray.Cast() + Array itemArray = (Array)itemProperty.GetValue(info.Instance); + List> formatted = itemArray.Cast() .Select(DebuggerAttributes.ValidateFullyDebuggerDisplayReferences) .Select(formattedResult => new KeyValuePair(formattedResult.Key, formattedResult.Value)) .ToList(); diff --git a/src/libraries/Common/tests/System/Diagnostics/DebuggerAttributes.cs b/src/libraries/Common/tests/System/Diagnostics/DebuggerAttributes.cs index 472694d0e567d0..f0211c2113c2d1 100644 --- a/src/libraries/Common/tests/System/Diagnostics/DebuggerAttributes.cs +++ b/src/libraries/Common/tests/System/Diagnostics/DebuggerAttributes.cs @@ -119,7 +119,7 @@ internal static string ValidateDebuggerDisplayReferences(object obj) private static CustomAttributeData FindAttribute(Type type, Type attributeType) { - for (var t = type; t != null; t = t.BaseType) + for (Type t = type; t != null; t = t.BaseType) { CustomAttributeData[] attributes = t.GetTypeInfo().CustomAttributes .Where(a => a.AttributeType == attributeType) @@ -156,7 +156,7 @@ private static string FormatDebuggerDisplayNamedArgument(string argumentName, Cu CustomAttributeNamedArgument namedAttribute = debuggerDisplayAttributeData.NamedArguments.FirstOrDefault(na => na.MemberName == argumentName); if (namedAttribute != default) { - var value = (string?)namedAttribute.TypedValue.Value; + string? value = (string?)namedAttribute.TypedValue.Value; if (!string.IsNullOrEmpty(value)) { return EvaluateDisplayString(value, obj); diff --git a/src/libraries/System.Collections.NonGeneric/tests/SortedListTests.cs b/src/libraries/System.Collections.NonGeneric/tests/SortedListTests.cs index 51ef75a58fa4ad..1b5378dfea8d3f 100644 --- a/src/libraries/System.Collections.NonGeneric/tests/SortedListTests.cs +++ b/src/libraries/System.Collections.NonGeneric/tests/SortedListTests.cs @@ -224,7 +224,7 @@ public void DebuggerAttribute_NormalList() var list = new SortedList() { { "a", 1 }, { "b", 2 } }; DebuggerAttributeInfo debuggerAttribute = DebuggerAttributes.ValidateDebuggerTypeProxyProperties(list); PropertyInfo itemProperty = debuggerAttribute.Properties.Single(pr => pr.GetCustomAttribute().State == DebuggerBrowsableState.RootHidden); - var itemArray = itemProperty.GetValue(debuggerAttribute.Instance) as Array; + Array itemArray = (Array)itemProperty.GetValue(debuggerAttribute.Instance); Assert.Equal(list.Count, itemArray.Length); } @@ -235,7 +235,7 @@ public void DebuggerAttribute_SynchronizedList() var list = SortedList.Synchronized(new SortedList() { { "a", 1 }, { "b", 2 } }); DebuggerAttributeInfo debuggerAttribute = DebuggerAttributes.ValidateDebuggerTypeProxyProperties(list); PropertyInfo itemProperty = debuggerAttribute.Properties.Single(pr => pr.GetCustomAttribute().State == DebuggerBrowsableState.RootHidden); - var itemArray = itemProperty.GetValue(debuggerAttribute.Instance) as Array; + Array itemArray = (Array)itemProperty.GetValue(debuggerAttribute.Instance); Assert.Equal(list.Count, itemArray.Length); } diff --git a/src/libraries/System.ObjectModel/tests/ReadOnlyDictionary/ReadOnlyDictionaryTests.cs b/src/libraries/System.ObjectModel/tests/ReadOnlyDictionary/ReadOnlyDictionaryTests.cs index bb9c565c61f166..e30fe1a48b9efc 100644 --- a/src/libraries/System.ObjectModel/tests/ReadOnlyDictionary/ReadOnlyDictionaryTests.cs +++ b/src/libraries/System.ObjectModel/tests/ReadOnlyDictionary/ReadOnlyDictionaryTests.cs @@ -239,7 +239,7 @@ public static void DebuggerAttributeTests() DebuggerAttributes.ValidateDebuggerDisplayReferences(dict); DebuggerAttributeInfo info = DebuggerAttributes.ValidateDebuggerTypeProxyProperties(dict); PropertyInfo itemProperty = info.Properties.Single(pr => pr.GetCustomAttribute().State == DebuggerBrowsableState.RootHidden); - var itemArray = itemProperty.GetValue(info.Instance) as Array; + Array itemArray = (Array)itemProperty.GetValue(info.Instance); Assert.Equal(dict.Count, itemArray.Length); DebuggerAttributes.ValidateDebuggerDisplayReferences(dict.Keys); diff --git a/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/DebugViewDictionaryItem.cs b/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/DebugViewDictionaryItem.cs index b528f79d792282..708b77a7c12c37 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/DebugViewDictionaryItem.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/DebugViewDictionaryItem.cs @@ -9,24 +9,24 @@ namespace System.Collections.Generic /// Defines a key/value pair for displaying an item of a dictionary by a debugger. /// [DebuggerDisplay("{Value}", Name = "[{Key}]")] - internal readonly struct DebugViewDictionaryItem + internal readonly struct DebugViewDictionaryItem { - public DebugViewDictionaryItem(K key, V value) + public DebugViewDictionaryItem(TKey key, TValue value) { Key = key; Value = value; } - public DebugViewDictionaryItem(KeyValuePair keyValue) + public DebugViewDictionaryItem(KeyValuePair keyValue) { Key = keyValue.Key; Value = keyValue.Value; } [DebuggerBrowsable(DebuggerBrowsableState.Collapsed)] - public K Key { get; init; } + public TKey Key { get; init; } [DebuggerBrowsable(DebuggerBrowsableState.Collapsed)] - public V Value { get; init; } + public TValue Value { get; init; } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/IDictionaryDebugView.cs b/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/IDictionaryDebugView.cs index 66e51a50811ecc..c082d31a09ff8b 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/IDictionaryDebugView.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/IDictionaryDebugView.cs @@ -5,11 +5,11 @@ namespace System.Collections.Generic { - internal sealed class IDictionaryDebugView where K : notnull + internal sealed class IDictionaryDebugView where TKey : notnull { - private readonly IDictionary _dict; + private readonly IDictionary _dict; - public IDictionaryDebugView(IDictionary dictionary) + public IDictionaryDebugView(IDictionary dictionary) { ArgumentNullException.ThrowIfNull(dictionary); @@ -17,16 +17,16 @@ public IDictionaryDebugView(IDictionary dictionary) } [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] - public DebugViewDictionaryItem[] Items + public DebugViewDictionaryItem[] Items { get { - var keyValuePairs = new KeyValuePair[_dict.Count]; + var keyValuePairs = new KeyValuePair[_dict.Count]; _dict.CopyTo(keyValuePairs, 0); - var items = new DebugViewDictionaryItem[keyValuePairs.Length]; + var items = new DebugViewDictionaryItem[keyValuePairs.Length]; for (int i = 0; i < items.Length; i++) { - items[i] = new DebugViewDictionaryItem(keyValuePairs[i]); + items[i] = new DebugViewDictionaryItem(keyValuePairs[i]); } return items; } From 347e36c9d955f30b9542d1a377dd37449f9a839f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Artur=20Zgodzi=C5=84ski?= Date: Wed, 1 Nov 2023 18:53:00 +0000 Subject: [PATCH 7/7] Minor improvents Renamed an internal method to match its new behavior and removed unnecessary init accessors. --- .../System/Collections/Generic/DebugViewDictionaryItem.cs | 4 ++-- .../src/System/Collections/Hashtable.cs | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/DebugViewDictionaryItem.cs b/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/DebugViewDictionaryItem.cs index 708b77a7c12c37..4088c1f2a4fdd9 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/DebugViewDictionaryItem.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/DebugViewDictionaryItem.cs @@ -24,9 +24,9 @@ public DebugViewDictionaryItem(KeyValuePair keyValue) } [DebuggerBrowsable(DebuggerBrowsableState.Collapsed)] - public TKey Key { get; init; } + public TKey Key { get; } [DebuggerBrowsable(DebuggerBrowsableState.Collapsed)] - public TValue Value { get; init; } + public TValue Value { get; } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Collections/Hashtable.cs b/src/libraries/System.Private.CoreLib/src/System/Collections/Hashtable.cs index a154a499271eb7..def27e549d8d2d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Collections/Hashtable.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Collections/Hashtable.cs @@ -582,7 +582,7 @@ public virtual void CopyTo(Array array, int arrayIndex) CopyEntries(array, arrayIndex); } - internal virtual DebugViewDictionaryItem[] ToKeyValuePairsArray() + internal virtual DebugViewDictionaryItem[] ToDebugViewDictionaryItemArray() { var array = new DebugViewDictionaryItem[_count]; int index = 0; @@ -1382,9 +1382,9 @@ public override void OnDeserialization(object? sender) // call OnDeserialization on our parent table. } - internal override DebugViewDictionaryItem[] ToKeyValuePairsArray() + internal override DebugViewDictionaryItem[] ToDebugViewDictionaryItemArray() { - return _table.ToKeyValuePairsArray(); + return _table.ToDebugViewDictionaryItemArray(); } } @@ -1506,7 +1506,7 @@ public HashtableDebugView(Hashtable hashtable) } [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] - public DebugViewDictionaryItem[] Items => _hashtable.ToKeyValuePairsArray(); + public DebugViewDictionaryItem[] Items => _hashtable.ToDebugViewDictionaryItemArray(); } } }