diff --git a/src/Controls/src/Core/ActivityIndicator/ActivityIndicator.cs b/src/Controls/src/Core/ActivityIndicator/ActivityIndicator.cs index 0d59f419a08b..306f27846196 100644 --- a/src/Controls/src/Core/ActivityIndicator/ActivityIndicator.cs +++ b/src/Controls/src/Core/ActivityIndicator/ActivityIndicator.cs @@ -1,6 +1,7 @@ #nullable disable using System; using System.Diagnostics; + using Microsoft.Maui.Graphics; namespace Microsoft.Maui.Controls @@ -45,7 +46,8 @@ public IPlatformElementConfiguration On() where T : ICo private protected override string GetDebuggerDisplay() { - return $"IsRunning = {IsRunning}, {base.GetDebuggerDisplay()}"; + var debugText = DebuggerDisplayHelpers.GetDebugText(nameof(IsRunning), IsRunning); + return $"{base.GetDebuggerDisplay()}, {debugText}"; } } } \ No newline at end of file diff --git a/src/Controls/src/Core/Button/Button.cs b/src/Controls/src/Core/Button/Button.cs index 071dedca2273..3ccba944ec54 100644 --- a/src/Controls/src/Core/Button/Button.cs +++ b/src/Controls/src/Core/Button/Button.cs @@ -6,6 +6,7 @@ using System.Runtime.CompilerServices; using System.Windows.Input; using Microsoft.Maui.Controls.Internals; + using Microsoft.Maui.Graphics; namespace Microsoft.Maui.Controls @@ -611,7 +612,9 @@ public override object ConvertTo(ITypeDescriptorContext context, CultureInfo cul private protected override string GetDebuggerDisplay() { - return $"Text = {Text}, Command = {Command}, {base.GetDebuggerDisplay()}"; + var textString = DebuggerDisplayHelpers.GetDebugText(nameof(Text), Text); + var commandText = DebuggerDisplayHelpers.GetDebugText(nameof(Command), Command, false); + return $"{base.GetDebuggerDisplay()}, {textString}, {commandText}"; } } } diff --git a/src/Controls/src/Core/CheckBox/CheckBox.cs b/src/Controls/src/Core/CheckBox/CheckBox.cs index 1ffd379f5c84..e622647662b0 100644 --- a/src/Controls/src/Core/CheckBox/CheckBox.cs +++ b/src/Controls/src/Core/CheckBox/CheckBox.cs @@ -84,7 +84,7 @@ bool ICheckBox.IsChecked private protected override string GetDebuggerDisplay() { - return $"IsChecked = {IsChecked}, {base.GetDebuggerDisplay()}"; + return $"{base.GetDebuggerDisplay()}, IsChecked = {IsChecked}"; } } } \ No newline at end of file diff --git a/src/Controls/src/Core/ContentPage/ContentPage.cs b/src/Controls/src/Core/ContentPage/ContentPage.cs index f6cc3ecc59df..e71bd13d22a0 100644 --- a/src/Controls/src/Core/ContentPage/ContentPage.cs +++ b/src/Controls/src/Core/ContentPage/ContentPage.cs @@ -2,6 +2,7 @@ using System; using System.Diagnostics; + using Microsoft.Maui.Graphics; using Microsoft.Maui.HotReload; using Microsoft.Maui.Layouts; @@ -152,7 +153,8 @@ Size IContentView.CrossPlatformMeasure(double widthConstraint, double heightCons private protected override string GetDebuggerDisplay() { - return $"Content = {Content}, BindingContext = {BindingContext}"; + var contentText = DebuggerDisplayHelpers.GetDebugText(nameof(Content), Content); + return $"{base.GetDebuggerDisplay()}, {contentText}"; } } } \ No newline at end of file diff --git a/src/Controls/src/Core/ContentView/ContentView.cs b/src/Controls/src/Core/ContentView/ContentView.cs index 87f29f99efa6..b56663e0012b 100644 --- a/src/Controls/src/Core/ContentView/ContentView.cs +++ b/src/Controls/src/Core/ContentView/ContentView.cs @@ -1,5 +1,6 @@ #nullable disable using System.Diagnostics; + using Microsoft.Maui.Graphics; using Microsoft.Maui.Layouts; @@ -51,7 +52,8 @@ internal override void SetChildInheritedBindingContext(Element child, object con private protected override string GetDebuggerDisplay() { - return $"Content = {Content}, {base.GetDebuggerDisplay()}"; + var contentText = DebuggerDisplayHelpers.GetDebugText(nameof(Content), Content); + return $"{base.GetDebuggerDisplay()}, {contentText}"; } } } \ No newline at end of file diff --git a/src/Controls/src/Core/DatePicker/DatePicker.cs b/src/Controls/src/Core/DatePicker/DatePicker.cs index c7a51ed35dee..de39e32d2f54 100644 --- a/src/Controls/src/Core/DatePicker/DatePicker.cs +++ b/src/Controls/src/Core/DatePicker/DatePicker.cs @@ -244,7 +244,7 @@ string IDatePicker.Format private protected override string GetDebuggerDisplay() { - return $"Date = {Date}, {base.GetDebuggerDisplay()}"; + return $"{base.GetDebuggerDisplay()}, Date = {Date}"; } } } diff --git a/src/Controls/src/Core/FlyoutPage/FlyoutPage.cs b/src/Controls/src/Core/FlyoutPage/FlyoutPage.cs index 86f064c311f8..366cb65df060 100644 --- a/src/Controls/src/Core/FlyoutPage/FlyoutPage.cs +++ b/src/Controls/src/Core/FlyoutPage/FlyoutPage.cs @@ -2,8 +2,10 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using System.Diagnostics; using System.Runtime.CompilerServices; using Microsoft.Maui.Controls.Internals; + using Microsoft.Maui.Devices; using Microsoft.Maui.Graphics; @@ -11,6 +13,7 @@ namespace Microsoft.Maui.Controls { /// [ContentProperty(nameof(Detail))] + [DebuggerDisplay("{GetDebuggerDisplay(), nq}")] public partial class FlyoutPage : Page, IFlyoutPageController, IElementConfiguration, IFlyoutView { /// Bindable property for . @@ -383,7 +386,8 @@ double IFlyoutView.FlyoutWidth #endif private protected override string GetDebuggerDisplay() { - return $"DetailPage = {Detail}, FlyoutPage = {Flyout}, BindingContext = {BindingContext}"; + var debugText = DebuggerDisplayHelpers.GetDebugText(nameof(Detail), Detail, "FlyoutPage", Flyout, nameof(BindingContext), BindingContext); + return $"{GetType().FullName}: {debugText}"; } } } diff --git a/src/Controls/src/Core/Image/Image.cs b/src/Controls/src/Core/Image/Image.cs index 18f2649bfc70..5eb9e799f1fd 100644 --- a/src/Controls/src/Core/Image/Image.cs +++ b/src/Controls/src/Core/Image/Image.cs @@ -3,6 +3,7 @@ using System.ComponentModel; using System.Diagnostics; + namespace Microsoft.Maui.Controls { /// @@ -108,7 +109,8 @@ void IImageSourcePart.UpdateIsLoading(bool isLoading) => private protected override string GetDebuggerDisplay() { - return $"Source = {Source}, {base.GetDebuggerDisplay()}"; + var sourceText = DebuggerDisplayHelpers.GetDebugText(nameof(Source), Source); + return $"{base.GetDebuggerDisplay()}, {sourceText}"; } } } \ No newline at end of file diff --git a/src/Controls/src/Core/IndicatorView/IndicatorView.cs b/src/Controls/src/Core/IndicatorView/IndicatorView.cs index e04c4d0bde2d..b62fea1516a2 100644 --- a/src/Controls/src/Core/IndicatorView/IndicatorView.cs +++ b/src/Controls/src/Core/IndicatorView/IndicatorView.cs @@ -4,6 +4,7 @@ using System.Collections.Specialized; using System.Diagnostics; using Microsoft.Maui.Controls.Internals; + using Microsoft.Maui.Graphics; using Microsoft.Maui.Layouts; @@ -11,7 +12,6 @@ namespace Microsoft.Maui.Controls { /// [ContentProperty(nameof(IndicatorLayout))] - [DebuggerDisplay("{GetDebuggerDisplay(), nq}")] public partial class IndicatorView : TemplatedView, ITemplatedIndicatorView { @@ -199,7 +199,8 @@ int IIndicatorView.Position private protected override string GetDebuggerDisplay() { - return $"Position = {Position}, Count = {Count}, {base.GetDebuggerDisplay()}"; + var debugText = DebuggerDisplayHelpers.GetDebugText(nameof(Position), Position, nameof(Count), Count); + return $"{base.GetDebuggerDisplay()}, {debugText}"; } } } \ No newline at end of file diff --git a/src/Controls/src/Core/InputView/InputView.cs b/src/Controls/src/Core/InputView/InputView.cs index 57b85ccf0efc..d74ab5480e6f 100644 --- a/src/Controls/src/Core/InputView/InputView.cs +++ b/src/Controls/src/Core/InputView/InputView.cs @@ -2,6 +2,7 @@ using System; using System.Diagnostics; using Microsoft.Maui.Controls.Internals; + using Microsoft.Maui.Graphics; namespace Microsoft.Maui.Controls @@ -276,7 +277,8 @@ string ITextInput.Text private protected override string GetDebuggerDisplay() { - return $"Text = {Text}, {base.GetDebuggerDisplay()}"; + var debugText = DebuggerDisplayHelpers.GetDebugText(nameof(Text), Text); + return $"{base.GetDebuggerDisplay()}, Text = {debugText}"; } } } \ No newline at end of file diff --git a/src/Controls/src/Core/Items/ItemsView.cs b/src/Controls/src/Core/Items/ItemsView.cs index 3b165590eafe..cd0b0d820d7b 100644 --- a/src/Controls/src/Core/Items/ItemsView.cs +++ b/src/Controls/src/Core/Items/ItemsView.cs @@ -3,15 +3,18 @@ using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Diagnostics; using System.Windows.Input; using Microsoft.Maui.Controls.Internals; using Microsoft.Maui.Controls.Xaml.Diagnostics; + using Microsoft.Maui.Devices; using Microsoft.Maui.Graphics; namespace Microsoft.Maui.Controls { /// + [DebuggerDisplay("{GetDebuggerDisplay(), nq}")] public abstract class ItemsView : View { @@ -228,7 +231,8 @@ protected override void OnBindingContextChanged() private protected override string GetDebuggerDisplay() { - return $"ItemsSource = {ItemsSource}, {base.GetDebuggerDisplay()}"; + var itemsSourceText = DebuggerDisplayHelpers.GetDebugText(nameof(ItemsSource), ItemsSource); + return $"{base.GetDebuggerDisplay()}, {itemsSourceText}"; } } } diff --git a/src/Controls/src/Core/Label/Label.cs b/src/Controls/src/Core/Label/Label.cs index 06d98ccd2bb9..dc5fceea5721 100644 --- a/src/Controls/src/Core/Label/Label.cs +++ b/src/Controls/src/Core/Label/Label.cs @@ -7,6 +7,7 @@ using System.Diagnostics; using System.Linq; using Microsoft.Maui.Controls.Internals; + using Microsoft.Maui.Graphics; namespace Microsoft.Maui.Controls @@ -482,7 +483,8 @@ internal static bool TextChangedShouldInvalidateMeasure(Label label) private protected override string GetDebuggerDisplay() { - return $"Text = {Text}, {base.GetDebuggerDisplay()}"; + var debugText = DebuggerDisplayHelpers.GetDebugText(nameof(Text), Text); + return $"{base.GetDebuggerDisplay()}, {debugText}"; } } } diff --git a/src/Controls/src/Core/Layout/Layout.cs b/src/Controls/src/Core/Layout/Layout.cs index da595b6dbccc..9f29c845569d 100644 --- a/src/Controls/src/Core/Layout/Layout.cs +++ b/src/Controls/src/Core/Layout/Layout.cs @@ -388,7 +388,7 @@ static void OnCascadeInputTransparentPropertyChanged(BindableObject bindable, ob private protected override string GetDebuggerDisplay() { - return $"ChildCount = {Count}, {base.GetDebuggerDisplay()}"; + return $"{base.GetDebuggerDisplay()}, ChildCount = {Count}"; } } } diff --git a/src/Controls/src/Core/Page/Page.cs b/src/Controls/src/Core/Page/Page.cs index 750eebe34c82..39ff6d2c2eaf 100644 --- a/src/Controls/src/Core/Page/Page.cs +++ b/src/Controls/src/Core/Page/Page.cs @@ -10,6 +10,7 @@ using System.Threading.Tasks; using Microsoft.Maui.Controls.Internals; using Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific; + using Microsoft.Maui.Graphics; namespace Microsoft.Maui.Controls @@ -943,7 +944,8 @@ public virtual Window GetParentWindow() private protected override string GetDebuggerDisplay() { - return $"BindingContext = {BindingContext}, Title = {Title}"; + var debugText = DebuggerDisplayHelpers.GetDebugText(nameof(BindingContext), BindingContext, nameof(Title), Title); + return $"{this.GetType().FullName}: {debugText}"; } } } diff --git a/src/Controls/src/Core/Picker/Picker.cs b/src/Controls/src/Core/Picker/Picker.cs index 612d3a223bf9..399235196aa4 100644 --- a/src/Controls/src/Core/Picker/Picker.cs +++ b/src/Controls/src/Core/Picker/Picker.cs @@ -9,6 +9,7 @@ using System.Runtime.CompilerServices; using Microsoft.Maui.Controls.Internals; using Microsoft.Maui.Controls.Xaml; + using Microsoft.Maui.Graphics; namespace Microsoft.Maui.Controls @@ -465,7 +466,8 @@ string GetItem(int index) private protected override string GetDebuggerDisplay() { - return $"Items = {ItemsSource?.Count ?? 0}, SelectedItem = {SelectedItem}, {base.GetDebuggerDisplay()}"; + var selectedItemText = DebuggerDisplayHelpers.GetDebugText(nameof(SelectedItem), SelectedItem); + return $"{base.GetDebuggerDisplay()}, Items = {ItemsSource?.Count ?? 0}, {selectedItemText}"; } } } diff --git a/src/Controls/src/Core/ProgressBar/ProgressBar.cs b/src/Controls/src/Core/ProgressBar/ProgressBar.cs index ebaba1231b05..f57a5817515e 100644 --- a/src/Controls/src/Core/ProgressBar/ProgressBar.cs +++ b/src/Controls/src/Core/ProgressBar/ProgressBar.cs @@ -58,7 +58,7 @@ public IPlatformElementConfiguration On() where T : IConfigPl private protected override string GetDebuggerDisplay() { - return $"Progress = {Progress}, {base.GetDebuggerDisplay()}"; + return $"{base.GetDebuggerDisplay()}, Progress = {Progress}"; } } } \ No newline at end of file diff --git a/src/Controls/src/Core/RadioButton/RadioButton.cs b/src/Controls/src/Core/RadioButton/RadioButton.cs index 9ab9a47a59e2..6a40dedf8119 100644 --- a/src/Controls/src/Core/RadioButton/RadioButton.cs +++ b/src/Controls/src/Core/RadioButton/RadioButton.cs @@ -4,6 +4,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Maui.Controls.Internals; using Microsoft.Maui.Controls.Shapes; + using Microsoft.Maui.Devices; using Microsoft.Maui.Graphics; @@ -665,7 +666,8 @@ bool IRadioButton.IsChecked private protected override string GetDebuggerDisplay() { - return $"IsChecked = {IsChecked}, Value = {Value}, {base.GetDebuggerDisplay()}"; + var debugText = DebuggerDisplayHelpers.GetDebugText(nameof(Value), Value, nameof(IsChecked), IsChecked); + return $"{base.GetDebuggerDisplay()},{debugText}"; } private protected override Semantics UpdateSemantics() diff --git a/src/Controls/src/Core/RefreshView/RefreshView.cs b/src/Controls/src/Core/RefreshView/RefreshView.cs index 5d057762121c..1c5848377a3c 100644 --- a/src/Controls/src/Core/RefreshView/RefreshView.cs +++ b/src/Controls/src/Core/RefreshView/RefreshView.cs @@ -4,6 +4,7 @@ using System.Runtime.CompilerServices; using System.Windows.Input; using Microsoft.Maui.Controls.Internals; + using Microsoft.Maui.Graphics; namespace Microsoft.Maui.Controls @@ -152,7 +153,8 @@ bool IRefreshView.IsRefreshing private protected override string GetDebuggerDisplay() { - return $"Command = {Command}, IsRefreshing = {IsRefreshing}, {base.GetDebuggerDisplay()}"; + var debugText = DebuggerDisplayHelpers.GetDebugText(nameof(Command), Command, nameof(IsRefreshing), IsRefreshing, false); + return $"{base.GetDebuggerDisplay()}, {debugText}"; } } } diff --git a/src/Controls/src/Core/ScrollView/ScrollView.cs b/src/Controls/src/Core/ScrollView/ScrollView.cs index a9b961b05930..23489e87fb54 100644 --- a/src/Controls/src/Core/ScrollView/ScrollView.cs +++ b/src/Controls/src/Core/ScrollView/ScrollView.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.Threading.Tasks; using Microsoft.Maui.Controls.Internals; + using Microsoft.Maui.Graphics; using Microsoft.Maui.Layouts; @@ -484,7 +485,8 @@ private protected override void InvalidateMeasureLegacy(InvalidationTrigger trig private protected override string GetDebuggerDisplay() { - return $"Content = {Content}, {base.GetDebuggerDisplay()}"; + var debugText = DebuggerDisplayHelpers.GetDebugText(nameof(Content), Content); + return $"{base.GetDebuggerDisplay()}, {debugText}"; } } } diff --git a/src/Controls/src/Core/SearchBar/SearchBar.cs b/src/Controls/src/Core/SearchBar/SearchBar.cs index 8bd390aa25de..36c0be9e3e45 100644 --- a/src/Controls/src/Core/SearchBar/SearchBar.cs +++ b/src/Controls/src/Core/SearchBar/SearchBar.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.Windows.Input; using Microsoft.Maui.Controls.Internals; + using Microsoft.Maui.Graphics; namespace Microsoft.Maui.Controls @@ -167,7 +168,8 @@ void ISearchBar.SearchButtonPressed() private protected override string GetDebuggerDisplay() { - return $"SearchCommand = {SearchCommand}, {base.GetDebuggerDisplay()}"; + var debugText = DebuggerDisplayHelpers.GetDebugText(nameof(SearchCommand), SearchCommand); + return $"{base.GetDebuggerDisplay()}, {debugText}"; } } } diff --git a/src/Controls/src/Core/Slider/Slider.cs b/src/Controls/src/Core/Slider/Slider.cs index adb45da3c9ed..5575636cf511 100644 --- a/src/Controls/src/Core/Slider/Slider.cs +++ b/src/Controls/src/Core/Slider/Slider.cs @@ -187,7 +187,7 @@ void ISlider.DragStarted() private protected override string GetDebuggerDisplay() { - return $"Value = {Value}, Min-Max = {Minimum} - {Maximum}, {base.GetDebuggerDisplay()}"; + return $"{base.GetDebuggerDisplay()}, Value = {Value}, Min-Max = {Minimum} - {Maximum}"; } } } \ No newline at end of file diff --git a/src/Controls/src/Core/Stepper/Stepper.cs b/src/Controls/src/Core/Stepper/Stepper.cs index 8089134c20ec..f68bc03f2971 100644 --- a/src/Controls/src/Core/Stepper/Stepper.cs +++ b/src/Controls/src/Core/Stepper/Stepper.cs @@ -112,7 +112,7 @@ public double Value private protected override string GetDebuggerDisplay() { - return $"Value = {Value}, {base.GetDebuggerDisplay()}"; + return $"{base.GetDebuggerDisplay()}, Value = {Value}"; } } } \ No newline at end of file diff --git a/src/Controls/src/Core/Switch/Switch.cs b/src/Controls/src/Core/Switch/Switch.cs index b0dbafccfabb..739855f14738 100644 --- a/src/Controls/src/Core/Switch/Switch.cs +++ b/src/Controls/src/Core/Switch/Switch.cs @@ -112,7 +112,7 @@ bool ISwitch.IsOn private protected override string GetDebuggerDisplay() { - return $"IsToggled = {IsToggled}, {base.GetDebuggerDisplay()}"; + return $"{base.GetDebuggerDisplay()}, IsToggled = {IsToggled}"; } } } \ No newline at end of file diff --git a/src/Controls/src/Core/TimePicker/TimePicker.cs b/src/Controls/src/Core/TimePicker/TimePicker.cs index 896d2acb4eea..e91fc153d4b0 100644 --- a/src/Controls/src/Core/TimePicker/TimePicker.cs +++ b/src/Controls/src/Core/TimePicker/TimePicker.cs @@ -171,7 +171,7 @@ static void TimePropertyChanged(BindableObject bindable, object oldValue, object private protected override string GetDebuggerDisplay() { - return $"Time = {Time}, {base.GetDebuggerDisplay()}"; + return $"{base.GetDebuggerDisplay()}, Time = {Time}"; } } } diff --git a/src/Controls/src/Core/VisualElement/VisualElement.cs b/src/Controls/src/Core/VisualElement/VisualElement.cs index 02b03f2a4ce2..b56b4ddbd672 100644 --- a/src/Controls/src/Core/VisualElement/VisualElement.cs +++ b/src/Controls/src/Core/VisualElement/VisualElement.cs @@ -6,6 +6,7 @@ using System.Globalization; using Microsoft.Maui.Controls.Internals; using Microsoft.Maui.Controls.Shapes; + using Microsoft.Maui.Graphics; using Microsoft.Maui.Layouts; using Geometry = Microsoft.Maui.Controls.Shapes.Geometry; @@ -19,7 +20,7 @@ namespace Microsoft.Maui.Controls /// /// The base class for most .NET MAUI on-screen elements. Provides most properties, events, and methods for presenting an item on screen. /// - + [DebuggerDisplay("{GetDebuggerDisplay(), nq}")] public partial class VisualElement : NavigableElement, IAnimatable, IVisualElementController, IResourcesProvider, IStyleElement, IFlowDirectionController, IPropertyPropagationController, IVisualController, IWindowController, IView, IControlsVisualElement { @@ -2402,10 +2403,10 @@ public override object ConvertTo(ITypeDescriptorContext context, CultureInfo cul } } - private protected virtual string GetDebuggerDisplay() { - return $"BindingContext = {BindingContext}, Bounds = {Bounds}"; + var debugText = DebuggerDisplayHelpers.GetDebugText(nameof(BindingContext), BindingContext, nameof(Bounds), Bounds); + return $"{GetType().FullName}: {debugText}"; } } } diff --git a/src/Controls/src/Core/WebView/WebView.cs b/src/Controls/src/Core/WebView/WebView.cs index d11daa6f9758..48bbb4aedca9 100644 --- a/src/Controls/src/Core/WebView/WebView.cs +++ b/src/Controls/src/Core/WebView/WebView.cs @@ -7,6 +7,7 @@ using System.Text.RegularExpressions; using System.Threading.Tasks; using Microsoft.Maui.Controls.Internals; + using Microsoft.Maui.Devices; namespace Microsoft.Maui.Controls @@ -388,7 +389,8 @@ void IWebView.ProcessTerminated(WebProcessTerminatedEventArgs args) private protected override string GetDebuggerDisplay() { - return $"Source = {Source}, {base.GetDebuggerDisplay()}"; + var debugText = DebuggerDisplayHelpers.GetDebugText(nameof(Source), Source); + return $"{base.GetDebuggerDisplay()}, {debugText}"; } } } \ No newline at end of file diff --git a/src/Core/src/Debugger/DebuggerDisplayHelpers.cs b/src/Core/src/Debugger/DebuggerDisplayHelpers.cs new file mode 100644 index 000000000000..b446707d5b12 --- /dev/null +++ b/src/Core/src/Debugger/DebuggerDisplayHelpers.cs @@ -0,0 +1,114 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using System.Text; + +namespace Microsoft.Maui; +/// +/// Inspired on https://github.com/dotnet/aspnetcore/blob/befc463e34b17edf8aee1fbf9dec44c75f13000b/src/Shared/Debugger/DebuggerHelpers.cs from Asp.NET +/// +static class DebuggerDisplayHelpers +{ + const string NullString = "(null)"; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string GetDebugText(string key1, object? value1, bool includeNullValues = true) + { + return GetDebugText([Create(key1, value1)], includeNullValues); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string GetDebugText(string key1, object? value1, string key2, object? value2, bool includeNullValues = true) + { + return GetDebugText([Create(key1, value1), Create(key2, value2)], includeNullValues); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string GetDebugText(string key1, object? value1, string key2, object? value2, string key3, object? value3, bool includeNullValues = true) + { + return GetDebugText([Create(key1, value1), Create(key2, value2), Create(key3, value3)], includeNullValues); + } + + public static string GetDebugText(ReadOnlySpan> values, bool includeNullValues = true) + { + var size = values.Length; + if (size is 0) + { + return string.Empty; + } + + var sb = new StringBuilder(); + + var first = true; + for (var i = 0; i < size; i++) + { + var kvp = values[i]; + + if (HasValue(kvp.Value) || includeNullValues) + { + if (first) + { + first = false; + } + else + { + sb.Append(", "); + } + + sb.Append(kvp.Key); + sb.Append(" = "); + if (kvp.Value is null) + { + sb.Append(NullString); + } + else if (kvp.Value is string s) + { + sb.Append(s); + } + else if (kvp.Value is IEnumerable enumerable) + { + var firstItem = true; + foreach (var item in enumerable) + { + if (firstItem) + { + firstItem = false; + } + else + { + sb.Append(','); + } + sb.Append(item); + } + } + else + { + sb.Append(kvp.Value); + } + } + } + + return sb.ToString(); + } + + static bool HasValue([NotNullWhen(true)] object? value) + { + if (value is null) + { + return false; + } + + // Empty collections don't have a value. + if (value is not string && value is IEnumerable enumerable && !enumerable.GetEnumerator().MoveNext()) + { + return false; + } + + return true; + } + + static KeyValuePair Create(string key, object? value) => new KeyValuePair(key, value); +}