Créer des visualiseurs de débogueur Visual Studio
Les visualiseurs de débogueur sont une fonctionnalité Visual Studio qui fournit une visualisation personnalisée pour les variables ou les objets d’un type .NET spécifique pendant une session de débogage.
Les visualiseurs de débogueur sont accessibles à partir de l’info-bulle qui apparaît lors du pointage sur une variable ou à partir des fenêtres Autos, Locals et Watch :
Bien démarrer
Suivez la section Créer le projet d’extension dans la section Prise en main.
Ensuite, ajoutez une classe qui étend DebuggerVisualizerProvider
et appliquez l’attribut VisualStudioContribution
à celui-ci :
/// <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);
}
}
Le code précédent définit un nouveau visualiseur de débogueur, qui s’applique aux objets de type string
:
- La
DebuggerVisualizerProviderConfiguration
propriété définit le nom complet du visualiseur et le type .NET pris en charge. - La
CreateVisualizerAsync
méthode est appelée par Visual Studio lorsque l’utilisateur demande l’affichage du visualiseur du débogueur pour une certaine valeur.CreateVisualizerAsync
utilise l’objetVisualizerTarget
pour récupérer la valeur à visualiser et la transmet à un contrôle utilisateur distant personnalisé (référencez la documentation de l’interface utilisateur distante). Le contrôle utilisateur distant est ensuite retourné et s’affiche dans une fenêtre contextuelle dans Visual Studio.
Ciblage de plusieurs types
La propriété de configuration permet au visualiseur de cibler plusieurs types lorsqu’il est pratique. Voici un exemple parfait du visualiseur DataSet qui prend en charge la visualisation des objets, et DataTable
DataView
DataViewManager
des DataSet
objets. Cette fonctionnalité facilite le développement d’extensions, car des types similaires peuvent partager la même interface utilisateur, les modèles d’affichage et la même source d’objet visualiseur.
/// <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)
{
...
}
Source de l’objet visualiseur
La source de l’objet visualiseur est une classe .NET chargée par le débogueur dans le processus en cours de débogage. Le visualiseur du débogueur peut récupérer des données à partir de la source de l’objet visualiseur à l’aide de méthodes exposées par VisualizerTarget.ObjectSource
.
La source d’objet visualiseur par défaut permet aux visualiseurs de débogueur de récupérer la valeur de l’objet à visualiser en appelant la RequestDataAsync<T>(JsonSerializer?, CancellationToken)
méthode. La source d’objet visualiseur par défaut utilise Newtonsoft.Json pour sérialiser la valeur, et les bibliothèques VisualStudio.Extensibility utilisent également Newtonsoft.Json pour la désérialisation. Vous pouvez également utiliser RequestDataAsync(CancellationToken)
pour récupérer la valeur sérialisée en tant que JToken
.
Si vous souhaitez visualiser un type .NET pris en charge en mode natif par Newtonsoft.Json ou que vous souhaitez visualiser votre propre type et vous pouvez le rendre sérialisable, les instructions précédentes sont suffisantes pour créer un visualiseur de débogueur simple. Lisez ce qui suit si vous souhaitez prendre en charge des types plus complexes ou utiliser des fonctionnalités plus avancées.
Utiliser une source d’objet visualiseur personnalisée
Si le type à visualiser ne peut pas être sérialisé automatiquement par Newtonsoft.Json, vous pouvez créer une source d’objet visualiseur personnalisée pour gérer la sérialisation.
- Créez un projet de bibliothèque de classes .NET ciblant
netstandard2.0
. Vous pouvez cibler une version plus spécifique de .NET Framework ou .NET (par exemple,net472
ounet6.0
) si nécessaire pour sérialiser l’objet à visualiser. - Ajoutez une référence de package à la
DebuggerVisualizers
version 17.6 ou ultérieure. - Ajoutez une classe qui étend
VisualizerObjectSource
et remplaceGetData
l’écriture de la valeur sérialisée dutarget
outgoingData
flux.
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
...
}
}
Utiliser la sérialisation personnalisée
Vous pouvez utiliser la VisualizerObjectSource.SerializeAsJson
méthode pour sérialiser un objet à l’aide de Newtonsoft.Json vers un Stream
sans ajouter de référence à Newtonsoft.Json à votre bibliothèque. L’appel SerializeAsJson
se charge, via la réflexion, une version de l’assembly Newtonsoft.Json dans le processus en cours de débogage.
Si vous devez référencer Newtonsoft.Json, vous devez utiliser la même version que celle référencée par le Microsoft.VisualStudio.Extensibility.Sdk
package, mais il est préférable d’utiliser et DataMember
d’attribuer DataContract
des attributs pour prendre en charge la sérialisation d’objets au lieu de compter sur les types Newtonsoft.Json.
Vous pouvez également implémenter votre propre sérialisation personnalisée (par exemple, la sérialisation binaire) en écrivant directement dans outgoingData
.
Ajouter la DLL source de l’objet visualiseur à l’extension
Modifiez le fichier d’extension .csproj
en ajoutant un ProjectReference
projet de bibliothèque de source d’objet visualiseur, ce qui garantit que la bibliothèque de source d’objet visualiseur est générée avant l’empaquetage de l’extension.
Ajoutez également un Content
élément incluant la DLL de bibliothèque de source d’objet du visualiseur dans le netstandard2.0
sous-dossier de l’extension.
<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>
Vous pouvez également utiliser les sous-dossiers ou netcoreapp
les net4.6.2
sous-dossiers si vous avez créé la bibliothèque de source d’objet visualiseur ciblant .NET Framework ou .NET. Vous pouvez même inclure les trois sous-dossiers avec différentes versions de la bibliothèque source de l’objet visualiseur, mais il est préférable de cibler netstandard2.0
uniquement.
Vous devez essayer de réduire le nombre de dépendances de la DLL de bibliothèque source d’objet visualiseur. Si votre bibliothèque de source d’objet visualiseur a des dépendances autres que Microsoft.VisualStudio.DebuggerVisualizers et bibliothèques qui sont déjà garanties d’être chargées dans le processus en cours de débogage, veillez à inclure également ces fichiers DLL dans le même sous-dossier que la DLL de bibliothèque de source d’objet visualiseur.
Mettre à jour le fournisseur de visualiseur du débogueur pour utiliser la source d’objet du visualiseur personnalisé
Vous pouvez ensuite mettre à jour votre DebuggerVisualizerProvider
configuration pour référencer votre source d’objet visualiseur personnalisée :
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);
}
Utiliser des objets volumineux et complexes
Si la récupération des données à partir de la source d’objet visualiseur ne peut pas être effectuée avec un seul appel sans paramètre, RequestDataAsync
vous pouvez plutôt effectuer un échange de messages plus complexe avec la source de l’objet visualiseur en appelant RequestDataAsync<TMessage, TResponse>(TMessage, JsonSerializer?, CancellationToken)
plusieurs fois et en envoyant différents messages à la source de l’objet visualiseur. Le message et la réponse sont sérialisés par l’infrastructure VisualStudio.Extensibility à l’aide de Newtonsoft.Json. D’autres remplacements vous permettent d’utiliser RequestDataAsync
JToken
des objets ou d’implémenter la sérialisation personnalisée et la désérialisation.
Vous pouvez implémenter n’importe quel protocole personnalisé à l’aide de différents messages pour récupérer des informations à partir de la source de l’objet visualiseur. Le cas d’usage le plus courant pour cette fonctionnalité est de briser la récupération d’un objet potentiellement volumineux en plusieurs appels afin d’éviter RequestDataAsync
le délai d’attente.
Voici un exemple de la façon dont vous pouvez récupérer le contenu d’une collection potentiellement volumineuse d’un élément à la fois :
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);
}
Le code ci-dessus utilise un index simple comme message pour les RequestDataAsync
appels. Le code source de l’objet visualiseur correspondant remplacerait la TransferData
méthode (au lieu de 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
...
}
}
La source de l’objet visualiseur ci-dessus tire parti de la VisualizerObjectSource.DeserializeFromJson
méthode pour désérialiser le message envoyé par le fournisseur de visualiseur à partir de incomingData
.
Lors de l’implémentation d’un fournisseur de visualiseur de débogueur qui effectue une interaction de message complexe avec la source de l’objet visualiseur, il est généralement préférable de passer les VisualizerTarget
données au visualiseur RemoteUserControl
afin que l’échange de messages puisse se produire de manière asynchrone pendant le chargement du contrôle. La transmission des VisualizerTarget
messages à la source d’objet du visualiseur vous permet également de récupérer des données en fonction des interactions de l’utilisateur avec l’interface utilisateur du visualiseur.
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
...
}
...
Ouverture de visualiseurs en tant qu’outil Windows
Par défaut, toutes les extensions du visualiseur de débogueur sont ouvertes en tant que fenêtres de dialogue modales au premier plan de Visual Studio. Par conséquent, si l’utilisateur souhaite continuer à interagir avec l’IDE, le visualiseur doit être fermé. Toutefois, si la Style
propriété est définie ToolWindow
dans la DebuggerVisualizerProviderConfiguration
propriété, le visualiseur est ouvert en tant que fenêtre d’outil non modale qui peut rester ouverte pendant le reste de la session de débogage. Si aucun style n’est déclaré, la valeur ModalDialog
par défaut est utilisée.
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);
}
Chaque fois qu’un visualiseur choisit d’être ouvert en tant que ToolWindow
, il devra s’abonner à l’événement StateChanged de l’objet VisualizerTarget
. Lorsqu’un visualiseur est ouvert en tant que fenêtre d’outil, il ne empêche pas l’utilisateur de supprimer la session de débogage. Par conséquent, l’événement mentionné ci-dessus sera déclenché par le débogueur chaque fois que l’état de la cible de débogage change. Les auteurs d’extensions du visualiseur doivent prêter une attention particulière à ces notifications, car la cible du visualiseur est disponible uniquement lorsque la session de débogage est active et que la cible de débogage est suspendue. Lorsque la cible du visualiseur n’est pas disponible, les appels aux ObjectSource
méthodes échouent avec un 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");
}
}
}
}
La Available
notification est reçue après la création de la RemoteUserControl
notification et juste avant qu’elle ne soit rendue visible dans la fenêtre de l’outil de visualiseur nouvellement créée. Tant que le visualiseur reste ouvert, les autres VisualizerTargetStateNotification
valeurs peuvent être reçues chaque fois que la cible de débogage change son état. La ValueUpdated
notification est utilisée pour indiquer que la dernière expression ouverte par le visualiseur a été correctement réévaluée où le débogueur s’est arrêté et doit être actualisé par l’interface utilisateur. En revanche, chaque fois que la cible de débogage est reprise ou que l’expression ne peut pas être réévaluée après l’arrêt, la Unavailable
notification est reçue.
Mettre à jour la valeur de l’objet visualisées
Si VisualizerTarget.IsTargetReplaceable
la valeur est true, le visualiseur du débogueur peut utiliser la ReplaceTargetObjectAsync
méthode pour mettre à jour la valeur de l’objet visualisé dans le processus en cours de débogage.
La source de l’objet visualiseur doit remplacer la CreateReplacementObject
méthode :
public override object CreateReplacementObject(object target, Stream incomingData)
{
// Use DeserializeFromJson to read from incomingData
// the new value of the object being visualized
...
return newValue;
}
Contenu connexe
Essayez l’exemple RegexMatchDebugVisualizer
pour voir ces techniques en action.