Vytváření vizualizérů ladicího programu sady Visual Studio

Vizualizéry ladicího programu jsou funkce sady Visual Studio, která poskytuje vlastní vizualizaci proměnných nebo objektů konkrétního typu .NET během ladicí relace.

Vizualizéry ladicího programu jsou přístupné z datového tipu, který se zobrazí při najetí myší na proměnnou nebo z oken Automatické hodnoty, Místní hodnoty a Kukátku:

Screenshot of debugger visualizers in the watch window.

Začínáme

Postupujte podle oddílu Vytvořit projekt rozšíření v části Začínáme.

Pak přidejte rozšiřující třídu DebuggerVisualizerProvider a použijte na VisualStudioContribution ni atribut:

/// <summary>
/// Debugger visualizer provider class for <see cref="System.String"/>.
/// </summary>
[VisualStudioContribution]
internal class StringDebuggerVisualizerProvider : DebuggerVisualizerProvider
{
    /// <summary>
    /// Initializes a new instance of the <see cref="StringDebuggerVisualizerProvider"/> class.
    /// </summary>
    /// <param name="extension">Extension instance.</param>
    /// <param name="extensibility">Extensibility object.</param>
    public StringDebuggerVisualizerProvider(StringDebuggerVisualizerExtension extension, VisualStudioExtensibility extensibility)
        : base(extension, extensibility)
    {
    }

    /// <inheritdoc/>
    public override DebuggerVisualizerProviderConfiguration DebuggerVisualizerProviderConfiguration => new("My string visualizer", typeof(string));

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

        return new MyStringVisualizerControl(targetObjectValue);
    }
}

Předchozí kód definuje nový vizualizér ladicího programu, který se vztahuje na objekty typu string:

  • Vlastnost DebuggerVisualizerProviderConfiguration definuje zobrazovaný název vizualizéru a podporovaný typ .NET.
  • Metoda CreateVisualizerAsync je vyvolána sadou Visual Studio, když uživatel požádá o zobrazení vizualizéru ladicího programu pro určitou hodnotu. CreateVisualizerAsync pomocí objektu VisualizerTarget načte hodnotu, která se má vizualizovat, a předá ji vlastnímu vzdálenému uživatelskému ovládacímu prvku (odkaz na dokumentaci k vzdálenému uživatelskému rozhraní ). Vzdálený uživatelský ovládací prvek se pak vrátí a zobrazí se v automaticky otevírané okně v sadě Visual Studio.

Cílení na více typů

Vlastnost konfigurace umožňuje vizualizéru cílit na více typů, pokud je to vhodné. Dokonalým příkladem je Vizualizér datové sady, který podporuje vizualizaci DataSetobjektů , DataViewDataTable, a DataViewManager objektů. Tato funkce usnadňuje vývoj rozšíření, protože podobné typy můžou sdílet stejné uživatelské rozhraní, zobrazit modely a zdroj objektů vizualizéru.

    /// <inheritdoc/>
    public override DebuggerVisualizerProviderConfiguration DebuggerVisualizerProviderConfiguration => new DebuggerVisualizerProviderConfiguration(
        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)));

    /// <inheritdoc/>
    public override async Task<IRemoteUserControl> CreateVisualizerAsync(VisualizerTarget visualizerTarget, CancellationToken cancellationToken)
    {
        ...
    }

Zdroj objektu vizualizéru

Zdroj objektu vizualizéru je třída .NET, která je načtena ladicím programem v procesu ladění. Vizualizér ladicího programu může načíst data ze zdroje objektu vizualizéru pomocí metod vystavených metodou VisualizerTarget.ObjectSource.

Výchozí zdroj objektu vizualizéru umožňuje vizualizérům ladicího programu načíst hodnotu objektu, která se má vizualizovat voláním RequestDataAsync<T>(JsonSerializer?, CancellationToken) metody. Výchozí zdroj objektu vizualizéru používá k serializaci hodnoty Newtonsoft.Json a knihovny VisualStudio.Extensibility také pro deserializaci používají Newtonsoft.Json. Alternativně můžete použít RequestDataAsync(CancellationToken) k načtení serializované hodnoty jako JToken.

Pokud chcete vizualizovat typ .NET, který je nativně podporován Newtonsoft.Json, nebo chcete vizualizovat vlastní typ a můžete ho serializovat, předchozí pokyny stačí k vytvoření jednoduchého vizualizéru ladicího programu. Přečtěte si, jestli chcete podporovat složitější typy nebo používat pokročilejší funkce.

Použití vlastního zdroje objektů vizualizéru

Pokud typ, který chcete vizualizovat, nelze automaticky serializovat pomocí Newtonsoft.Json, můžete vytvořit vlastní zdroj vizualizéru objektu pro zpracování serializace.

  • Vytvořte nový projekt knihovny tříd .NET, který cílí netstandard2.0na . V případě potřeby můžete cílit na konkrétnější verzi rozhraní .NET Framework nebo .NET (například net472 ) net6.0k serializaci objektu, který chcete vizualizovat.
  • Přidejte odkaz na balíček verze DebuggerVisualizers 17.6 nebo novější.
  • Přidejte třídu, která rozšiřuje VisualizerObjectSource a přepisuje GetData zápis serializované hodnoty do datového outgoingDatatarget proudu.
public class MyObjectSource : VisualizerObjectSource
{
    /// <inheritdoc/>
    public override void GetData(object target, Stream outgoingData)
    {
        MySerializableType result = Convert(match);
        SerializeAsJson(outgoingData, result);
    }

    private static MySerializableType Convert(object target)
    {
        // Add your code here to convert target into a type serializable by Newtonsoft.Json
        ...
    }
}

Použití vlastní serializace

Tuto metodu VisualizerObjectSource.SerializeAsJson můžete použít k serializaci objektu pomocí Newtonsoft.Json na objekt Stream bez přidání odkazu na Newtonsoft.Json do knihovny. Vyvolání SerializeAsJson se načte prostřednictvím reflexe verze sestavení Newtonsoft.Json do laděného procesu.

Pokud potřebujete odkazovat na Newtonsoft.Json, měli byste použít stejnou verzi, na kterou odkazuje Microsoft.VisualStudio.Extensibility.Sdk balíček, ale je vhodnější použít DataContract a DataMember atributy pro podporu serializace objektů místo toho, abyste se museli spoléhat na typy Newtonsoft.Json.

Alternativně můžete implementovat vlastní serializace (například binární serializace) zápis přímo do outgoingData.

Přidání knihovny DLL zdrojového objektu vizualizéru do rozšíření

Upravte soubor přípony .csprojProjectReference přidáním do projektu zdrojové knihovny vizualizéru objektů, který zajistí, že je zdrojová knihovna vizualizéru před zabaleným rozšířením sestavena.

Přidejte Content také položku včetně knihovny DLL zdrojové knihovny vizualizéru do netstandard2.0 podsložky rozšíření.

  <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>

Alternativně můžete použít net4.6.2netcoreapp podsložky, pokud jste vytvořili zdrojová knihovna vizualizéru, která cílí na rozhraní .NET Framework nebo .NET. Můžete dokonce zahrnout všechny tři podsložky s různými verzemi zdrojové knihovny vizualizéru objektů, ale je lepší cílit netstandard2.0 pouze na ně.

Měli byste se pokusit minimalizovat počet závislostí knihovny DLL zdrojové knihovny vizualizéru objektů. Pokud zdrojová knihovna vizualizéru obsahuje jiné závislosti než Microsoft.VisualStudio.DebuggerVisualizers a knihovny, které jsou již zaručeny pro načtení v procesu ladění, nezapomeňte tyto soubory DLL zahrnout do stejné podsložky jako knihovna DLL vizualizéru zdrojové knihovny DLL.

Aktualizace zprostředkovatele vizualizéru ladicího programu tak, aby používala vlastní zdroj objektů vizualizéru

Potom můžete aktualizovat DebuggerVisualizerProvider konfiguraci tak, aby odkazovat na zdroj vlastního vizualizéru:

    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);
    }

Práce s velkými a složitými objekty

Pokud načtení dat ze zdroje objektu vizualizéru nejde provést s jediným voláním RequestDataAsyncbez parametrů , můžete místo toho provést složitější výměnu zpráv se zdrojem vizualizéru objektu tak, že několikrát vyvoláte RequestDataAsync<TMessage, TResponse>(TMessage, JsonSerializer?, CancellationToken) a odešlete různé zprávy do zdroje vizualizéru objektu. Zprávu i odpověď serializuje infrastruktura VisualStudio.Extensibility pomocí Newtonsoft.Json. Další přepsání RequestDataAsync umožňují používat JToken objekty nebo implementovat vlastní serializaci a deserializaci.

Pomocí různých zpráv můžete implementovat libovolný vlastní protokol, který načte informace ze zdroje vizualizéru objektů. Nejběžnějším případem použití této funkce je přerušení načítání potenciálně velkého objektu na několik volání, aby se zabránilo časovému RequestDataAsync limitu.

Toto je příklad, jak můžete načíst obsah potenciálně velké kolekce po jedné položce:

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);
}

Výše uvedený kód používá jednoduchý index jako zprávu pro RequestDataAsync volání. Odpovídající zdrojový kód vizualizéru by přepsal metodu TransferData (místo 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);
        }
    }

    private static MySerializableType Convert(object target)
    {
        // Add your code here to convert target into a type serializable by Newtonsoft.Json
        ...
    }
}

Výše uvedený zdroj vizualizéru využívá metodu VisualizerObjectSource.DeserializeFromJson k deserializaci zprávy odeslané poskytovatelem vizualizéru z incomingData.

Při implementaci zprostředkovatele vizualizéru ladicího programu, který provádí složitou interakci se zdrojem objektu vizualizéru, je obvykle lepší předat VisualizerTarget vizualizéru RemoteUserControl , aby se výměna zpráv stala asynchronně při načtení ovládacího prvku. VisualizerTarget Předáním také můžete odesílat zprávy do zdroje objektu vizualizéru za účelem načtení dat na základě interakcí uživatele s uživatelským rozhraním vizualizéru.

public override Task<IRemoteUserControl> CreateVisualizerAsync(VisualizerTarget visualizerTarget, CancellationToken cancellationToken)
{
    return Task.FromResult<IRemoteUserControl>(new MyVisualizerUserControl(visualizerTarget));
}
internal class MyVisualizerUserControl : RemoteUserControl
{
    private readonly VisualizerTarget visualizerTarget;

    public MyVisualizerUserControl(VisualizerTarget visualizerTarget)
        : base(new MyDataContext())
    {
        this.visualizerTarget = visualizerTarget;
    }

    public override async Task ControlLoadedAsync(CancellationToken cancellationToken)
    {
        // Start querying the VisualizerTarget here
        ...
    }
    ...

Otevírání vizualizérů ve Windows nástrojů

Ve výchozím nastavení se všechna rozšíření vizualizéru ladicího programu otevírají jako modální dialogová okna v popředí sady Visual Studio. Proto pokud chce uživatel pokračovat v interakci s integrovaným vývojovém prostředím ( IDE), musí být vizualizér zavřený. Pokud je však Style vlastnost nastavena na ToolWindow vlastnost DebuggerVisualizerProviderConfiguration , bude vizualizér otevřen jako nemodální okno nástroje, které může zůstat otevřené ve zbytku ladicí relace. Pokud není deklarován žádný styl, použije se výchozí hodnota ModalDialog .

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

    public override async Task<IRemoteUserControl> CreateVisualizerAsync(VisualizerTarget visualizerTarget, CancellationToken cancellationToken)
    {
        // The control will be in charge of calling the RequestDataAsync method from the visualizer object source and disposing of the visualizer target.
        return new MyVisualizerUserControl(visualizerTarget);
    }

Kdykoli se vizualizér rozhodne být otevřen jako , ToolWindowbude muset přihlásit k odběru StateChanged události VisualizerTarget. Když je vizualizér otevřen jako okno nástroje, nezablokuje uživatele v pozastavení ladicí relace. Výše uvedená událost se tedy aktivuje ladicím programem při každé změně stavu cíle ladění. Autoři rozšíření Vizualizéru by měli věnovat zvláštní pozornost těmto oznámením, protože cíl vizualizéru je k dispozici pouze v případě, že je aktivní ladicí relace a cíl ladění je pozastavený. Pokud cíl vizualizéru není k dispozici, volání metod ObjectSource selže s chybou 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;
            visualizerTarget.StateChanged += 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 visualizerTarget.ObjectSource.RequestDataAsync<MySerializableType>(jsonSerializer: null, CancellationToken.None);
                    break;
                case VisualizerTargetStateNotification.Unavailable:
                    Value = null;
                    break;
                default:
                    throw new NotSupportedException("Unexpected visualizer target state notification");
            }
        }
    }
}

Oznámení Available se obdrží po RemoteUserControl vytvoření a těsně před tím, než se zobrazí v nově vytvořeném okně nástroje vizualizéru. Pokud vizualizér zůstane otevřený, ostatní hodnoty lze přijímat pokaždé, VisualizerTargetStateNotification když cíl ladění změní svůj stav. Oznámení ValueUpdated se používá k označení, že poslední výraz otevřený vizualizérem byl úspěšně znovu vyhodnocen, kde ladicí program zastavil a měl by být aktualizován uživatelským rozhraním. Na druhé straně, kdykoli je cíl ladění obnoven nebo výraz nelze po zastavení znovu vyhodnotit, Unavailable oznámení se obdrží.

Aktualizace vizualizované hodnoty objektu

Pokud VisualizerTarget.IsTargetReplaceable je pravda, vizualizér ladicího programu může použít metodu ReplaceTargetObjectAsync k aktualizaci hodnoty vizualizovaného objektu v procesu ladění.

Zdroj objektu vizualizéru musí přepsat metodu CreateReplacementObject :

public override object CreateReplacementObject(object target, Stream incomingData)
{
    // Use DeserializeFromJson to read from incomingData
    // the new value of the object being visualized
    ...
    return newValue;
}

Vyzkoušejte si ukázku RegexMatchDebugVisualizer a podívejte se na tyto techniky v akci.