Visualizers voor debuggers

Visualisaties voor foutopsporingsprogramma's zijn een Visual Studio-functie die een aangepaste visualisatie biedt voor variabelen of objecten van een specifiek .NET-type tijdens een foutopsporingssessie. Debuggervisualizers zijn toegankelijk via de DataTip die wordt weergegeven wanneer u de muisaanwijzer boven een variabele beweegt, of vanuit de vensters Autos, Locals en Watch.

Note

In dit artikel wordt de VisualStudio.Extensibility SDK gebruikt. Zie Het juiste uitbreidbaarheidsmodel kiezen voor hulp bij het kiezen tussen VisualStudio.Extensibility en de verouderde VSSDK.

Schermopname van debuggervisualizers in het venster Watch.

Volledig voorbeeld

De volledige implementatie bevindt zich in het voorbeeld:

// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace RegexMatchVisualizer;

using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.Extensibility;
using Microsoft.VisualStudio.Extensibility.DebuggerVisualizers;
using Microsoft.VisualStudio.RpcContracts.RemoteUI;
using RegexMatchVisualizer.ObjectSource;

/// <summary>
/// Debugger visualizer provider class for <see cref="Match"/>.
/// </summary>
[VisualStudioContribution]
internal class RegexMatchDebuggerVisualizerProvider : DebuggerVisualizerProvider
{
    /// <inheritdoc/>
    public override DebuggerVisualizerProviderConfiguration DebuggerVisualizerProviderConfiguration => new("%RegexMatchVisualizer.RegexMatchDebuggerVisualizerProvider.DisplayName%", typeof(Match))
    {
        VisualizerObjectSourceType = new("RegexMatchVisualizer.ObjectSource.RegexMatchObjectSource, RegexMatchObjectSource"),
    };

    /// <inheritdoc/>
    public override async Task<IRemoteUserControl> CreateVisualizerAsync(VisualizerTarget visualizerTarget, CancellationToken cancellationToken)
    {
        var regexMatch = await visualizerTarget.ObjectSource.RequestDataAsync<RegexMatch>(jsonSerializer: null, cancellationToken);

        return new RegexMatchVisualizerUserControl(regexMatch);
    }
}

Tip

Bekijk het volledige werkende voorbeeld: RegexMatchDebugVisualizer ↗

Verwante concepten:Remote UI, bijdragen en configuraties en uw eerste extensie maken

Begrijp het voorbeeld

Get started

Maak eerst een VisualStudio.Extensibility-extensieproject door de instructies in Uw eerste extensie maken te volgen.

Elke extensie begint met een Extension toegangspunt dat ExtensionConfiguration metagegevens definieert:

[VisualStudioContribution]
internal class MyExtension : Extension
{
    public override ExtensionConfiguration ExtensionConfiguration => new()
    {
        Metadata = new(
            id: "MyExtension.01234567",
            version: this.ExtensionAssemblyVersion,
            publisherName: "Contoso",
            displayName: "My debugger visualizer extension"),
    };
}

De debuggervisualizerprovider voor de debugger maken

Voeg een klasse toe die VisualStudioContribution ↗ uitbreidt en pas het kenmerk DebuggerVisualizerProvider ↗ erop toe:

[VisualStudioContribution]
internal class StringDebuggerVisualizerProvider : DebuggerVisualizerProvider
{
    public override DebuggerVisualizerProviderConfiguration DebuggerVisualizerProviderConfiguration => new(
        "My string visualizer",
        typeof(string));

    public override async Task<IRemoteUserControl> CreateVisualizerAsync(
        VisualizerTarget visualizerTarget,
        CancellationToken cancellationToken)
    {
        string targetObjectValue = await visualizerTarget.ObjectSource.RequestDataAsync<string>(
            jsonSerializer: null,
            cancellationToken);

        return new MyStringVisualizerControl(targetObjectValue);
    }
}

Deze code definieert een nieuwe debuggervisualisator voor string-waarden:

  • DebuggerVisualizerProviderConfiguration definieert de weergavenaam van de visualizer en het ondersteunde .NET-type.
  • CreateVisualizerAsync wordt aangeroepen wanneer de gebruiker de visualizer opent. Het gebruikt VisualizerTarget ↗ om gegevens op te halen en retourneert een Remote UI-besturingselement dat Visual Studio in een pop-upvenster weergeeft.

In hetzelfde configuratieobject declareert u details zoals de doeltypen, weergavenamen van visualizers, VisualizerObjectSourceType, en of de visualizer wordt geopend als een modaal dialoogvenster of als een niet-modaal toolvenster.

Meerdere typen selecteren

De configuratie-eigenschap kan meerdere typen als doel hebben wanneer het zinvol is om dezelfde gebruikersinterface, viewmodels en de objectbron van de visualizer te delen. Een veelvoorkomend voorbeeld is een visualisatie in tabelvorm die ondersteuning biedt voor DataSet, DataTable, DataViewManager en DataView.

public override DebuggerVisualizerProviderConfiguration DebuggerVisualizerProviderConfiguration => new(
    new VisualizerTargetType("DataSet Visualizer", typeof(System.Data.DataSet)),
    new VisualizerTargetType("DataTable Visualizer", typeof(System.Data.DataTable)),
    new VisualizerTargetType("DataView Visualizer", typeof(System.Data.DataView)),
    new VisualizerTargetType("DataViewManager Visualizer", typeof(System.Data.DataViewManager)));

Werken met de bron van het visualizer-object

De bron van het visualizer-object is een .NET-klasse die door het foutopsporingsprogramma wordt geladen in het proces waarvan fouten worden opgespoord. Uw debuggervisualizer haalt er gegevens uit op via VisualizerTarget.ObjectSource ↗.

De standaardbron voor visualizerobjecten ondersteunt:

De standaard implementatie maakt gebruik van Newtonsoft.Json ↗ voor serialisatie. Als het doeltype al serialiseerbaar is of als u uw eigen type serializeerbaar kunt maken, is deze standaardstroom vaak voldoende.

Een aangepaste visualisatieobjectbron gebruiken

Als het type dat moet worden gevisualiseerd niet automatisch kan worden geserialiseerd door Newtonsoft.Json, maakt u een aangepaste visualizer object source.

  1. Maak een nieuwe klassenbibliotheek die is gericht op netstandard2.0. Indien nodig kunt u zich richten op een specifiekere framework zoals net472 of net6.0.
  2. Voeg een pakketreferentie toe aan Microsoft.VisualStudio.DebuggerVisualizers ↗ versie 17.6 of hoger.
  3. Maak een klasse die VisualizerObjectSource ↗ uitbreidt en overschrijf GetData.
public class MyObjectSource : VisualizerObjectSource
{
    public override void GetData(object target, Stream outgoingData)
    {
        MySerializableType result = Convert(target);
        SerializeAsJson(outgoingData, result);
    }

    private static MySerializableType Convert(object target)
    {
        // Convert the debuggee object into a serializable type.
        throw new NotImplementedException();
    }
}

Als u het Regex-voorbeeld volgt, zet RegexMatchObjectSourceMatch om in een serialiseerbare RegexMatch-vorm voordat de provider dit aanvraagt.

Aangepaste serialisatie gebruiken

Gebruik VisualizerObjectSource.SerializeAsJson om objecten te serialiseren met Newtonsoft.Json zonder een directe Newtonsoft.Json-afhankelijkheid toe te voegen aan de objectbronbibliotheek. De methode laadt de Newtonsoft.Json-assembly in het gedebugde proces via reflectie.

Als u besluit om rechtstreeks naar Newtonsoft.Json te verwijzen, gebruikt u dezelfde versie waarnaar wordt verwezen door Microsoft.VisualStudio.Extensibility.Sdk. In de meeste gevallen is het beter om te gebruiken DataContract en DataMember kenmerken voor gedeelde typen in plaats van te vertrouwen op Newtonsoft.Json-specifieke typen.

U kunt ook uw eigen serialisatie-indeling implementeren en rechtstreeks naar outgoingData schrijven.

Het DLL-bestand van de visualr-objectbron toevoegen aan de extensie

Voeg een ProjectReference zodanig toe dat het objectbronproject wordt gebouwd voordat de extensie wordt verpakt en voeg een Content item toe zodat het DLL-bestand wordt gekopieerd naar de extensie-uitvoer.

<ItemGroup>
  <Content Include="pathToTheObjectSourceDllBinPath\$(Configuration)\netstandard2.0\MyObjectSourceLibrary.dll"
           Link="netstandard2.0\MyObjectSourceLibrary.dll">
    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
  </Content>
</ItemGroup>

<ItemGroup>
  <ProjectReference Include="..\MyObjectSourceLibrary\MyObjectSourceLibrary.csproj" />
</ItemGroup>

U kunt ook de submappen net462 of netstandard2.0 gebruiken als de objectbronbibliotheek op die frameworks is gericht, maar netcoreapp heeft waar mogelijk de voorkeur.

Houd de afhankelijkheidsgrafiek van de objectbronbibliotheek zo klein mogelijk. Als dit afhankelijk is van andere DLL's die het foutopsporingsdoel nog niet laadt, neemt u deze DLL's op in dezelfde extensiesubmap.

De provider configureren voor het gebruik van de aangepaste objectbron

Nadat de object-source DLL is verpakt, werkt u DebuggerVisualizerProviderConfiguration zo bij dat deze ernaar verwijst:

public override DebuggerVisualizerProviderConfiguration DebuggerVisualizerProviderConfiguration => new(
    "My visualizer",
    typeof(TypeToVisualize))
{
    VisualizerObjectSourceType = new(typeof(MyObjectSource)),
};
public override async Task<IRemoteUserControl> CreateVisualizerAsync(
    VisualizerTarget visualizerTarget,
    CancellationToken cancellationToken)
{
    MySerializableType result = await visualizerTarget.ObjectSource.RequestDataAsync<MySerializableType>(
        jsonSerializer: null,
        cancellationToken);

    return new MyVisualizerUserControl(result);
}

Als u opzettelijk een assemblyverwijzing van de extensie naar het objectbronproject vermijdt, configureert u VisualizerObjectSourceType met de assembly-gekwalificeerde naam in plaats van typeof(...). In het Regex-voorbeeld wordt dat patroon gebruikt.

Werken met grote en complexe objecten

Als het ophalen van alle gegevens in één parameterloze RequestDataAsync aanroep niet praktisch is, kunt u RequestDataAsync<TMessage, TResponse>(TMessage, JsonSerializer?, CancellationToken) ↗ gebruiken om berichten uit te wisselen met de objectbron.

Dit patroon is handig voor grote verzamelingen omdat elke RequestDataAsync aanroep een time-out van vijf seconden heeft. Een veelvoorkomende benadering is het aanvragen van items één voor één:

for (int i = 0; ; i++)
{
    MySerializableType? collectionEntry =
        await visualizerTarget.ObjectSource.RequestDataAsync<int, MySerializableType?>(
            i,
            jsonSerializer: null,
            cancellationToken);

    if (collectionEntry is null)
    {
        break;
    }

    observableCollection.Add(collectionEntry);
}

Aan de objectbronzijde overschrijft u TransferData in plaats van GetData:

public class MyCollectionTypeObjectSource : VisualizerObjectSource
{
    public override void TransferData(object target, Stream incomingData, Stream outgoingData)
    {
        var index = (int)DeserializeFromJson(incomingData, typeof(int))!;

        if (target is MyCollectionType collection && index < collection.Count)
        {
            var result = Convert(collection[index]);
            SerializeAsJson(outgoingData, result);
        }
        else
        {
            SerializeAsJson(outgoingData, null);
        }
    }
}

DeserializeFromJson leest het bericht dat de provider via incomingData heeft verzonden.

Wanneer een visualizer complexere berichtenuitwisseling uitvoert, is het vaak beter om het VisualizerTarget ↗ aan het externe gebruikersbesturingselement door te geven, zodat gegevens asynchroon kunnen worden aangevraagd terwijl het besturingselement wordt geladen of later als reactie op gebruikersinteractie:

public override Task<IRemoteUserControl> CreateVisualizerAsync(
    VisualizerTarget visualizerTarget,
    CancellationToken cancellationToken)
{
    return Task.FromResult<IRemoteUserControl>(new MyVisualizerUserControl(visualizerTarget));
}

Visualizers openen als hulpmiddelvensters

Debuggervisualisatoren worden standaard geopend als modale dialoogvensters op de voorgrond van Visual Studio. Als de gebruiker wil doorgaan met de interactie met de IDE, moet deze eerst de modale visualisator sluiten.

Als u Style = VisualizerStyle.ToolWindow instelt, wordt de visualisatie geopend als een niet-modaal toolvenster en kan deze geopend blijven voor de rest van de foutopsporingssessie. Als u Style niet instelt, is de standaardwaarde ModalDialog.

public override DebuggerVisualizerProviderConfiguration DebuggerVisualizerProviderConfiguration => new(
    "My visualizer",
    typeof(TypeToVisualize))
{
    Style = VisualizerStyle.ToolWindow
};

Wanneer een visualizer wordt geopend als een werksetvenster, moet deze zich abonneren op de VisualizerTarget.Changed gebeurtenis. Toolvensters blokkeren de gebruiker niet om de uitvoering te hervatten, zodat het foutopsporingsprogramma meldingen genereert wanneer de status van het foutopsporingsdoel verandert. Wanneer de doelserver niet beschikbaar is, mislukt het aanroepen van ObjectSource met VisualizerTargetUnavailableException.

internal class MyVisualizerUserControl : RemoteUserControl
{
    private readonly VisualizerDataContext dataContext;

#pragma warning disable CA2000 // Dispose objects before losing scope
    public MyVisualizerUserControl(VisualizerTarget visualizerTarget)
        : base(dataContext: new VisualizerDataContext(visualizerTarget))
#pragma warning restore CA2000 // Dispose objects before losing scope
    {
        this.dataContext = (VisualizerDataContext)this.DataContext!;
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            this.dataContext.Dispose();
        }
    }

    [DataContract]
    private class VisualizerDataContext : NotifyPropertyChangedObject, IDisposable
    {
        private readonly VisualizerTarget visualizerTarget;
        private MySerializableType? _value;

        public VisualizerDataContext(VisualizerTarget visualizerTarget)
        {
            this.visualizerTarget = visualizerTarget;
            this.visualizerTarget.Changed += this.OnStateChangedAsync;
        }

        [DataMember]
        public MySerializableType? Value
        {
            get => this._value;
            set => this.SetProperty(ref this._value, value);
        }

        public void Dispose()
        {
            this.visualizerTarget.Dispose();
        }

        private async Task OnStateChangedAsync(object? sender, VisualizerTargetStateNotification args)
        {
            switch (args)
            {
                case VisualizerTargetStateNotification.Available:
                case VisualizerTargetStateNotification.ValueUpdated:
                    Value = await this.visualizerTarget.ObjectSource.RequestDataAsync<MySerializableType>(
                        jsonSerializer: null,
                        CancellationToken.None);
                    break;
                case VisualizerTargetStateNotification.Unavailable:
                    Value = null;
                    break;
                default:
                    throw new NotSupportedException("Unexpected visualizer target state notification");
            }
        }
    }
}

Available wordt geactiveerd nadat het RemoteUserControl is gemaakt en net voordat Visual Studio het gereedschapsvenster weergeeft. ValueUpdated geeft aan dat de expressie opnieuw is geëvalueerd met de huidige onderbrekingsstatus en dat de gebruikersinterface moet worden vernieuwd. Unavailable geeft aan dat de uitvoering is hervat of dat de expressie niet opnieuw kon worden geëvalueerd nadat de uitvoering is gestopt.

De gevisualiseerde objectwaarde bijwerken

Als VisualizerTarget.IsTargetReplaceable ↗true is, kan de visualizer de waarde van het doelproces bijwerken door ReplaceTargetObjectAsync ↗ aan te roepen.

await visualizerTarget.ObjectSource.ReplaceTargetObjectAsync(
    newValue,
    jsonSerializer: null,
    cancellationToken);

De objectbron van de visualizer moet CreateReplacementObject overschrijven:

public override object CreateReplacementObject(object target, Stream incomingData)
{
    // Read the replacement value from incomingData.
    return newValue;
}

Tips

✅ DO

  • Plaats extensiemetagegevens in ExtensionConfiguration en bijdrage-instellingen in DebuggerVisualizerProviderConfiguration.
  • Gebruik netstandard2.0 voor het bronproject, tenzij u een specifieke reden hebt om een ander framework te richten.
  • Gebruik DataContract en DataMember op gedeelde gegevenstypen.
  • Verpak de object-source-DLL en eventuele vereiste afhankelijkheden met de extensie.
  • Gebruik gepaginade RequestDataAsync aanroepen voor grote of dure objecten.
  • Verwerk VisualizerTarget.Changed bij het openen als hulpmiddelvenster.

❌ NIET DOEN

  • Verwijs niet naar VisualStudio.Extensibility-pakketten uit het objectbronproject.
  • Ga er niet van uit dat elk type kan worden geserialiseerd door de default object source.
  • Laat geen visualisatiefunctie voor het toolvenster onwetend zijn van Unavailable-meldingen.
  • Vergeet niet dat RequestDataAsync oproepen na vijf seconden een time-out kunnen krijgen.

🔧 Problemen oplossen

Symptom Wat u moet controleren
Visualizer wordt niet weergegeven Controleer of de providerklasse [VisualStudioContribution] heeft en of DebuggerVisualizerProviderConfiguration gericht is op het juiste .NET-type.
RequestDataAsync mislukt of er treedt een time-out op Verplaats het ophalen van grote gegevens naar TransferData en pagineer de gegevens over meerdere aanroepen.
Aangepaste objectbron is niet gevonden Controleer of het DLL-bestand is gekopieerd naar de map netstandard2.0 van de extensie en VisualizerObjectSourceType verwijst naar het juiste type of de juiste assembly-gekwalificeerde naam.
Visualizer voor toolvensters geeft een uitzondering wanneer foutopsporing wordt voortgezet Verwerk VisualizerTargetStateNotification.Unavailable en vermijd ObjectSource-aanroepen totdat het doel weer beschikbaar is.

Volgende stappen