Remarque
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
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 du DataTip qui apparaît lors du survol d’une variable, ou à partir des fenêtres Autos, Locals et Watch.
Note
Cet article utilise le Kit de développement logiciel (SDK) VisualStudio.Extensibility . Pour obtenir de l’aide sur le choix entre VisualStudio.Extensibility et le VSSDK hérité, consultez Choisir le modèle d’extensibilité approprié.
Exemple complet
L’implémentation complète se trouve dans l’exemple :
// 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
Consultez l’exemple de travail complet : RegexMatchDebugVisualizer ↗
Concepts connexes :interface utilisateur distante, contributions et configurations, et créer votre première extension
Comprenez l’exemple
Get started
Pour commencer, créez un projet d’extension VisualStudio.Extensibility en suivant Créer votre première extension.
Chaque extension commence par un point d’entrée Extension qui définit les ExtensionConfiguration métadonnées :
[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"),
};
}
Créer le fournisseur de visualisation du débogueur
Ajoutez une classe qui étend DebuggerVisualizerProvider ↗ et appliquez l’attribut VisualStudioContribution ↗ à celui-ci :
[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);
}
}
Ce code définit un nouveau visualiseur de débogueur pour les valeurs string :
-
DebuggerVisualizerProviderConfigurationdéfinit le nom d’affichage du visualiseur et le type .NET pris en charge. -
CreateVisualizerAsyncest appelé lorsque l’utilisateur ouvre le visualiseur. Il utiliseVisualizerTarget↗ pour récupérer les données et renvoie un contrôle Remote UI que Visual Studio affiche dans une fenêtre contextuelle.
Le même objet de configuration est l’endroit où vous déclarez des détails tels que les types cibles, les noms d’affichage du visualiseur, VisualizerObjectSourceTypeet si le visualiseur s’ouvre en tant que boîte de dialogue modale ou en tant que fenêtre d’outil non modale.
Cibler plusieurs types
La propriété de configuration peut cibler plusieurs types lorsqu’il est judicieux de partager la même interface utilisateur, les modèles d’affichage et la même source d’objet visualiseur. Un exemple courant est un visualiseur tabulaire qui prend en charge DataSet, DataTable, DataViewet DataViewManager.
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)));
Travailler avec la source de l’objet visualiseur
La source de l'objet visualiseur est une classe .NET que le débogueur charge dans le processus en cours de débogage. Votre visualiseur de débogueur récupère des données à partir de celle-ci via VisualizerTarget.ObjectSource ↗ .
La source d’objet du visualiseur par défaut prend en charge :
-
RequestDataAsync<T>(JsonSerializer?, CancellationToken)↗ pour les données fortement typées -
RequestDataAsync(CancellationToken)↗ pourJToken
L’implémentation par défaut utilise Newtonsoft.Json ↗ pour la sérialisation. Si le type cible est déjà sérialisable ou si vous pouvez rendre votre propre type sérialisable, ce flux par défaut est souvent suffisant.
Utiliser une source d’objet visualiseur personnalisée
Si le type à visualiser ne peut pas être sérialisé automatiquement par Newtonsoft.Json, créez une source d’objet visualiseur personnalisée.
- Créez une bibliothèque de classes ciblant
netstandard2.0. Si nécessaire, vous pouvez cibler un framework plus spécifique tel quenet472ounet6.0. - Ajoutez une référence de package à Microsoft.VisualStudio.DebuggerVisualizers ↗ version 17.6 ou ultérieure.
- Créez une classe qui étend
VisualizerObjectSource↗ et redéfinitGetData.
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();
}
}
Si vous suivez l'exemple Regex, RegexMatchObjectSource convertit Match en une structure RegexMatch sérialisable avant que le fournisseur n'en fasse la demande.
Utiliser la sérialisation personnalisée
Utilisez VisualizerObjectSource.SerializeAsJson pour sérialiser des objets avec Newtonsoft.Json sans ajouter de dépendance directe à Newtonsoft.Json dans la bibliothèque de sources d'objets. La méthode charge l’assembly Newtonsoft.Json dans le processus débogué via la réflexion.
Si vous décidez de référencer Newtonsoft.Json directement, utilisez la même version référencée par Microsoft.VisualStudio.Extensibility.Sdk. Dans la plupart des cas, il est préférable d’utiliser les attributs DataMember et DataContract pour les types partagés au lieu de s’appuyer sur des types spécifiques à Newtonsoft.Json.
Vous pouvez également implémenter votre propre format de sérialisation et écrire directement vers outgoingData.
Ajouter la DLL source de l’objet visualiseur à l’extension
Ajoutez une ProjectReference afin que le projet de sources d'objets soit généré avant l'empaquetage de l'extension, et ajoutez un élément Content afin que le fichier DLL soit copié dans le dossier de sortie 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 net462 ou netcoreapp sous-dossiers si la bibliothèque source d’objets cible ces frameworks, mais netstandard2.0 est préférable si possible.
Réduisez autant que possible le graphe de dépendances de la bibliothèque de sources d'objets. S’il dépend d’autres DLL que le programme débogué ne charge pas déjà, incluez ces DLL dans le même sous-dossier d’extension.
Configurer le fournisseur pour utiliser la source d’objet personnalisée
Une fois la DLL source d’objet empaquetée, mettez à jour DebuggerVisualizerProviderConfiguration pour la pointer vers celle-ci :
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);
}
Si vous évitez intentionnellement une référence d’assembly de l’extension au projet source d’objet, configurez VisualizerObjectSourceType avec le nom qualifié d’assembly au lieu de typeof(...). L’exemple Regex utilise ce modèle.
Travailler avec des objets grands et complexes
Si la récupération de toutes les données en un seul appel à RequestDataAsync sans paramètre n'est pas envisageable, utilisez RequestDataAsync<TMessage, TResponse>(TMessage, JsonSerializer?, CancellationToken) ↗ pour échanger des messages avec la source d'objets.
Ce modèle est utile pour les grandes collections, car chaque appel à RequestDataAsync a un délai d’expiration de cinq secondes. Une approche courante consiste à demander des entrées une par une :
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);
}
Du côté de la source d'objets, substituez TransferData 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);
}
}
}
DeserializeFromJson lit le message que le fournisseur a envoyé via incomingData.
Lorsqu'un visualiseur effectue des échanges de messages plus complexes, il est souvent préférable de transmettre VisualizerTarget ↗ au contrôle utilisateur distant afin qu'il puisse demander des données de manière asynchrone pendant son chargement ou ultérieurement, en réponse à une interaction de l'utilisateur :
public override Task<IRemoteUserControl> CreateVisualizerAsync(
VisualizerTarget visualizerTarget,
CancellationToken cancellationToken)
{
return Task.FromResult<IRemoteUserControl>(new MyVisualizerUserControl(visualizerTarget));
}
Ouvrir les visualiseurs sous forme de fenêtres Outils
Par défaut, les visualiseurs de débogueur s’ouvrent en tant que boîtes de dialogue modales au premier plan de Visual Studio. Si l’utilisateur souhaite continuer à interagir avec l’IDE, il doit d’abord fermer le visualiseur modal.
Si vous définissez Style = VisualizerStyle.ToolWindow, le visualiseur s’ouvre en tant que fenêtre d’outil non modale et peut rester ouvert pour le reste de la session de débogage. Si vous ne définissez pas Style, la valeur par défaut est ModalDialog.
public override DebuggerVisualizerProviderConfiguration DebuggerVisualizerProviderConfiguration => new(
"My visualizer",
typeof(TypeToVisualize))
{
Style = VisualizerStyle.ToolWindow
};
Lorsqu’un visualiseur s’ouvre en tant que fenêtre d’outil, il doit s’abonner à l’événement VisualizerTarget.Changed . Les fenêtres d’outils ne empêchent pas l’utilisateur de reprendre l’exécution. Par conséquent, le débogueur déclenche des notifications chaque fois que la cible de débogage change d’état. Lorsque la cible n’est pas disponible, les appels à ObjectSource échouent avec 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 est déclenché après la création de RemoteUserControl et juste avant que Visual Studio affiche la fenêtre Outils.
ValueUpdated indique que l’expression a été correctement réévaluée à l’état d’arrêt actuel et que l’interface utilisateur doit s’actualiser.
Unavailable indique que l’exécution a repris son cours ou que l’expression n’a pas pu être réévaluée après l’arrêt de l’exécution.
Mettre à jour la valeur de l’objet visualisée
Si VisualizerTarget.IsTargetReplaceable ↗ c’est truele cas, le visualiseur peut mettre à jour la valeur du processus débogué en appelant ReplaceTargetObjectAsync ↗.
await visualizerTarget.ObjectSource.ReplaceTargetObjectAsync(
newValue,
jsonSerializer: null,
cancellationToken);
La source de l’objet visualiseur doit redéfinir CreateReplacementObject:
public override object CreateReplacementObject(object target, Stream incomingData)
{
// Read the replacement value from incomingData.
return newValue;
}
Tips
✅ DO
- Placez les métadonnées d’extension dans
ExtensionConfigurationet les paramètres de contribution dansDebuggerVisualizerProviderConfiguration. - Utilisez
netstandard2.0pour le projet de sources d'objets, sauf si vous avez une raison particulière de cibler un autre framework. - Utiliser
DataContractetDataMembersur les types de données partagés. - Incluez le fichier DLL de la source d'objets et toutes les dépendances requises dans le package de l'extension.
- Utilisez des appels paginés
RequestDataAsyncpour des objets volumineux ou coûteux. - Gérer
VisualizerTarget.Changedlors de l’ouverture en tant que fenêtre d’outil.
❌ NE PAS
- Ne référencez pas les packages VisualStudio.Extensibility à partir du projet object-source.
- Ne partez pas du principe que tous les types peuvent être sérialisés par la source d'objets par défaut.
- N'oubliez pas de faire en sorte que le visualiseur dans une fenêtre Outils prenne en compte les notifications
Unavailable. - N'oubliez pas que les appels
RequestDataAsyncpeuvent expirer au bout de cinq secondes.
🔧 Résolution des problèmes
| Symptôme | Que vérifier |
|---|---|
| Le visualiseur n’apparaît pas | Vérifiez que la classe fournisseur a [VisualStudioContribution] et que DebuggerVisualizerProviderConfiguration cible le type .NET correct. |
RequestDataAsync échoue ou expire |
Déplacez la récupération des données volumineuses vers TransferData et répartissez les données sur plusieurs appels à l'aide de la pagination. |
| La source d’objet personnalisée est introuvable | Vérifiez que la DLL est copiée dans le dossier netstandard2.0 de l’extension et VisualizerObjectSourceType pointe vers le type correct ou le nom qualifié d’assembly. |
| Le visualiseur dans la fenêtre Outils lève une exception lorsque le débogage reprend | Gérez VisualizerTargetStateNotification.Unavailable et évitez les appels à ObjectSource jusqu'à ce que la cible soit de nouveau disponible. |
Étapes suivantes
- Explorez l’exemple complet RegexMatchDebugVisualizer ↗.
- En savoir plus sur Remote UI.
- Passez en revue les contributions et les configurations.
- Comparez les modèles dans Choisir le modèle d’extensibilité approprié.