Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Visualizadores de depurador são um recurso do Visual Studio que fornece uma visualização personalizada para variáveis ou objetos de um tipo .NET específico durante uma sessão de depuração.
Os visualizadores do depurador podem ser acessados a partir da Dica de Dados exibida ao passar o mouse sobre uma variável, ou a partir das janelas Autos, Locais e Watch:
Introdução
Siga a seção Criar o projeto de extensão na seção Introdução.
Em seguida, adicione uma classe estendendo DebuggerVisualizerProvider e aplique o VisualStudioContribution atributo a ela:
/// <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);
}
}
O código anterior define um novo visualizador de depurador, que se aplica a objetos do tipo string:
- A
DebuggerVisualizerProviderConfigurationpropriedade define o nome de exibição do visualizador e o tipo .NET com suporte. - O
CreateVisualizerAsyncmétodo é invocado pelo Visual Studio quando o usuário solicita a exibição do visualizador do depurador para um determinado valor.CreateVisualizerAsyncusa oVisualizerTargetobjeto para recuperar o valor a ser visualizado e passa-o para um controle de usuário remoto personalizado (faça referência à documentação da interface do usuário remota ). Em seguida, o controle remoto do usuário é retornado e será mostrado em uma janela pop-up no Visual Studio.
Focando em múltiplos tipos
A propriedade de configuração permite que o visualizador direcione vários tipos quando conveniente. Um exemplo perfeito disso é o Visualizador de Conjunto de Dados que dá suporte à visualização de DataSet, DataTablee DataViewDataViewManager objetos. Essa funcionalidade facilita o desenvolvimento de extensão, pois tipos semelhantes podem compartilhar a mesma interface do usuário, modelos de exibição e fonte de objeto do visualizador.
/// <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)
{
...
}
A origem do objeto visualizador
A origem do objeto visualizador é uma classe .NET que é carregada pelo depurador no processo que está sendo depurado. O visualizador do depurador pode recuperar dados da fonte do objeto visualizador usando métodos expostos por VisualizerTarget.ObjectSource.
O objeto fonte do visualizador padrão permite que os visualizadores do depurador recuperem o valor do objeto a ser visualizado chamando o método RequestDataAsync<T>(JsonSerializer?, CancellationToken). A fonte de objeto do visualizador padrão usa Newtonsoft.Json para serializar o valor e as bibliotecas VisualStudio.Extensibility também usam Newtonsoft.Json para a desserialização. Como alternativa, você pode usar RequestDataAsync(CancellationToken) para recuperar o valor serializado como um JToken.
Se você quiser visualizar um tipo .NET com suporte nativo pelo Newtonsoft.Json ou quiser visualizar seu próprio tipo e torná-lo serializável, as instruções anteriores são suficientes para criar um visualizador de depurador simples. Leia se você deseja dar suporte a tipos mais complexos ou usar recursos mais avançados.
Usar uma fonte de objeto do visualizador personalizado
Se o tipo a ser visualizado não puder ser serializado automaticamente pelo Newtonsoft.Json, você poderá criar uma fonte de objeto do visualizador personalizado para lidar com a serialização.
- Crie um novo projeto de biblioteca de classes do .NET destinado a
netstandard2.0. Você pode direcionar uma versão mais específica do .NET Framework ou do .NET (por exemplo,net472ounet6.0) se necessário para serializar o objeto a ser visualizado. - Adicione uma referência de pacote à
DebuggerVisualizersversão 17.6 ou mais recente. - Adicione uma classe que herda de
VisualizerObjectSourcee sobrescrevaGetDatapara escrever o valor serializado detargetno fluxooutgoingData.
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
...
}
}
Usar serialização personalizada
Você pode usar o VisualizerObjectSource.SerializeAsJson método para serializar um objeto usando Newtonsoft.Json a um Stream sem adicionar uma referência a Newtonsoft.Json à biblioteca. A invocação SerializeAsJson carregará, por meio de reflexão, uma versão do assembly Newtonsoft.Json no processo que está sendo depurado.
Se você precisar fazer referência ao Newtonsoft.Json, deverá usar a mesma versão referenciada pelo Microsoft.VisualStudio.Extensibility.Sdk pacote, mas é preferível usar DataContract e DataMember atributos para dar suporte à serialização de objetos em vez de depender dos tipos Newtonsoft.Json.
Como alternativa, você pode implementar sua própria serialização personalizada (como serialização binária) gravando diretamente em outgoingData.
Adicionar a DLL de origem do objeto visualizador à extensão
Modifique o arquivo de extensão .csproj adicionando um ProjectReference ao projeto da biblioteca de origem do objeto visualizador, que garante que a biblioteca de origem do objeto do visualizador seja criada antes que a extensão seja empacotada.
Adicione também um Content item incluindo a DLL da biblioteca de origem do objeto visualizador na netstandard2.0 subpasta da extensão.
<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>
Como alternativa, você pode usar as subpastas net4.6.2 ou netcoreapp se tiver criado a biblioteca de origem do objeto visualizador voltada para .NET Framework ou .NET. Você pode até incluir todas as três subpastas com versões diferentes da biblioteca de código-fonte do objeto visualizador, mas é melhor direcionar apenas o netstandard2.0.
Você deve tentar minimizar o número de dependências da DLL da biblioteca de origem do objeto visualizador. Se a biblioteca de origem do objeto do visualizador tiver dependências diferentes de Microsoft.VisualStudio.DebuggerVisualizers e bibliotecas que já estão garantidas para serem carregadas no processo que está sendo depurado, inclua também esses arquivos DLL na mesma subpasta que a DLL da biblioteca de origem do objeto do visualizador.
Atualizar o fornecedor do visualizador do depurador para usar a fonte de objetos do visualizador personalizado
Em seguida, você pode atualizar sua DebuggerVisualizerProvider configuração para fazer referência à fonte de objeto do visualizador personalizado:
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);
}
Trabalhar com objetos grandes e complexos
Se a recuperação de dados da fonte de objeto do visualizador não puder ser feita com uma única chamada sem parâmetros para RequestDataAsync, em vez disso, você poderá executar uma troca de mensagens mais complexa com a fonte do objeto visualizador invocando RequestDataAsync<TMessage, TResponse>(TMessage, JsonSerializer?, CancellationToken) várias vezes e enviando mensagens diferentes para a fonte do objeto visualizador. A mensagem e a resposta são serializadas pela infraestrutura VisualStudio.Extensibility usando Newtonsoft.Json. As outras substituições de RequestDataAsync permitem que você use objetos JToken ou implemente serialização e desserialização personalizadas.
Você pode implementar qualquer protocolo personalizado usando mensagens diferentes para recuperar informações da origem do objeto visualizador. O caso de uso mais comum para essa funcionalidade é dividir a recuperação de um objeto potencialmente grande em chamadas múltiplas para evitar o estouro do tempo limite RequestDataAsync.
Este é um exemplo de como você pode recuperar o conteúdo de uma coleção potencialmente grande um item de cada vez:
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);
}
O código acima usa um índice simples como mensagem para as RequestDataAsync chamadas. O código-fonte do objeto visualizador correspondente substituiria o TransferData método (em vez 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
...
}
}
A origem do objeto visualizador acima utiliza o método VisualizerObjectSource.DeserializeFromJson para desserializar a mensagem enviada pelo provedor de visualização a partir de incomingData.
Ao implementar um provedor de visualizador de depurador que executa uma interação complexa de mensagens com a fonte do objeto do visualizador, geralmente é melhor passar o VisualizerTarget para o RemoteUserControl do visualizador para que a troca de mensagens possa ocorrer de forma assíncrona enquanto o controle é carregado. Passar também VisualizerTarget permite enviar mensagens para o objeto fonte do visualizador para recuperar dados com base nas interações do usuário com a interface do visualizador.
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
...
}
...
Abrir visualizadores como Janelas de Ferramentas
Por padrão, todas as extensões do visualizador de depurador são abertas como janelas de diálogo modais no primeiro plano do Visual Studio. Portanto, se o usuário quiser continuar a interagir com o IDE, o visualizador precisará ser fechado. No entanto, se a propriedade Style for definida como ToolWindow na propriedade DebuggerVisualizerProviderConfiguration, o visualizador será aberto como uma janela de ferramenta não modal que pode ficar aberta pelo restante da sessão de depuração. Se nenhum estilo for declarado, o valor ModalDialog padrão será usado.
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);
}
Sempre que um visualizador escolher ser aberto como um ToolWindow, ele precisará assinar o evento StateChanged do VisualizerTarget. Quando um visualizador é aberto como uma janela de ferramentas, ele não impedirá o usuário de desativar a sessão de depuração. Portanto, o evento mencionado acima será disparado pelo depurador sempre que o estado do alvo de depuração for alterado. Os autores da extensão do visualizador devem prestar atenção especial a essas notificações, pois o alvo do visualizador só está disponível quando a sessão de depuração está ativa e o alvo de depuração está pausado. Quando o alvo do visualizador não estiver disponível, as chamadas para os ObjectSource métodos falharão com um 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");
}
}
}
}
A Available notificação será recebida após a RemoteUserControl criação e logo antes de ficar visível na janela de ferramentas do visualizador recém-criada. Enquanto o visualizador permanecer aberto, os outros VisualizerTargetStateNotification valores poderão ser recebidos sempre que o alvo de depuração alterar seu estado. A ValueUpdated notificação é usada para indicar que a última expressão aberta pelo visualizador foi reavaliada com sucesso quando o depurador parou e deve ser atualizada pela interface do usuário. Por outro lado, sempre que o destino de depuração for reiniciado ou a expressão não puder ser reavaliada após a interrupção, a Unavailable notificação será recebida.
Atualizar o valor do objeto visualizado
Se VisualizerTarget.IsTargetReplaceable for verdadeiro, o visualizador do depurador poderá usar o método ReplaceTargetObjectAsync para atualizar o valor do objeto visualizado no processo que está sendo depurado.
A origem do objeto visualizador deve substituir o método 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;
}
Conteúdo relacionado
Experimente o RegexMatchDebugVisualizer exemplo para ver essas técnicas em ação.