Notatka
Dostęp do tej strony wymaga autoryzacji. Może spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Wizualizatory debugera to funkcja programu Visual Studio, która udostępnia niestandardową wizualizację zmiennych lub obiektów określonego typu platformy .NET podczas sesji debugowania. Wizualizatory debugera są dostępne z poziomu DataTip wyświetlanego po najechaniu kursorem na zmienną lub w oknach Autos, Locals i Watch.
Note
W tym artykule jest używany zestaw VisualStudio.Extensibility SDK. Aby uzyskać pomoc dotyczącą wyboru między programem VisualStudio.Extensibility i starszym zestawem VSSDK, zobacz Wybieranie odpowiedniego modelu rozszerzalności.
Pełny przykład
Kompletna implementacja znajduje się w przykładzie:
// 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
Zobacz pełny przykład roboczy: RegexMatchDebugVisualizer ↗
Powiązane pojęcia:Zdalny interfejs użytkownika, wkłady i konfiguracje oraz Tworzenie pierwszego rozszerzenia
Zrozum przykład
Wprowadzenie
Aby rozpocząć, utwórz projekt rozszerzenia VisualStudio.Extensibility, postępując zgodnie z instrukcjami w artykule Tworzenie pierwszego rozszerzenia.
Każde rozszerzenie rozpoczyna się od Extensionpunktu wejścia definiującego ExtensionConfigurationmetadane:
[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"),
};
}
Utwórz dostawcę wizualizatora debugera
Dodaj klasę, która rozszerza DebuggerVisualizerProvider ↗, i zastosuj do niej atrybut VisualStudioContribution ↗:
[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);
}
}
Ten kod definiuje nowy wizualizator debugera dla string wartości:
-
DebuggerVisualizerProviderConfigurationdefiniuje nazwę wyświetlaną wizualizatora i obsługiwany typ .NET. -
CreateVisualizerAsyncjest wywoływany po otwarciu wizualizatora przez użytkownika. UżywaVisualizerTarget↗ do pobierania danych i zwraca kontrolkę zdalnego interfejsu użytkownika, którą program Visual Studio wyświetla w oknie podręcznym.
Ten sam obiekt konfiguracji to miejsce, w którym deklarujesz szczegóły, takie jak typy docelowe, nazwy wyświetlane wizualizatora, VisualizerObjectSourceType oraz to, czy wizualizator otwiera się jako modalne okno dialogowe, czy jako niemodalne okno narzędzia.
Wybierz wiele typów
Właściwość konfiguracji może być skierowana do wielu typów, gdy warto współużytkować ten sam interfejs użytkownika, modele widoku i źródło obiektu wizualizatora. Typowym przykładem jest tabelaryczny wizualizator obsługujący DataSet, DataTable, DataView i 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)));
Praca z obiektem źródłowym wizualizatora
Obiekt źródłowy wizualizatora jest klasą .NET, którą debuger ładuje do debugowanego procesu. Wizualizator debugera pobiera z niego dane za pośrednictwem elementu VisualizerTarget.ObjectSource ↗ .
Domyślne źródło obiektu wizualizatora obsługuje:
-
RequestDataAsync<T>(JsonSerializer?, CancellationToken)↗ dla danych silnie typowanych -
RequestDataAsync(CancellationToken)↗ dlaJToken
Domyślna implementacja używa Newtonsoft.Json ↗ do serializacji. Jeśli typ docelowy jest już możliwy do serializacji lub jeśli można sprawić, by własny typ był serializowalny, ten przepływ domyślny jest często wystarczający.
Użycie niestandardowego źródła obiektu wizualizacji
Jeśli typ do wizualizacji nie może być automatycznie serializowany przez Newtonsoft.Json, utwórz niestandardowe źródło obiektu wizualizatora.
- Utwórz nową bibliotekę klas ukierunkowaną na
netstandard2.0. Jeśli jest to wymagane, możesz określić bardziej szczegółową platformę, taką jaknet472lubnet6.0. - Dodaj odwołanie do pakietu Microsoft.VisualStudio.DebuggerVisualizers ↗ w wersji 17.6 lub nowszej.
- Utwórz klasę, która rozszerza
VisualizerObjectSource↗ i przesłania elementGetData.
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();
}
}
Jeśli korzystasz z przykładu Regex, RegexMatchObjectSource konwertuje Match do postaci możliwej RegexMatch do serializacji, zanim dostawca zażąda go.
Używanie niestandardowej serializacji
Użyj VisualizerObjectSource.SerializeAsJson, aby serializować obiekty przy użyciu Newtonsoft.Json bez dodawania bezpośredniej zależności Newtonsoft.Json do biblioteki object-source. Metoda ładuje bibliotekę Newtonsoft.Json w procesie debuggee za pomocą mechanizmu refleksji.
Jeśli zdecydujesz się odwoływać bezpośrednio do Newtonsoft.Json, użyj tej samej wersji, do której odwołuje się Microsoft.VisualStudio.Extensibility.Sdk. W większości przypadków lepiej używać atrybutów DataMember i DataContract dla typów udostępnionych zamiast polegać na typach DataContract specyficznych dla Newtonsoft.Json.
Możesz również zaimplementować własny format serializacji i zapisać bezpośrednio do outgoingData.
Dodawanie źródłowej biblioteki DLL obiektu wizualizatora do rozszerzenia
Dodaj element ProjectReference, aby projekt object-source został skompilowany, zanim rozszerzenie zostanie spakowane, i dodaj element Content, aby biblioteka DLL została skopiowana do danych wyjściowych rozszerzenia.
<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>
Można również użyć podfolderów net462 lub netcoreapp, jeśli biblioteka źródłowa obiektów jest przeznaczona dla tych platform docelowych, ale netstandard2.0 jest preferowana, jeśli to możliwe.
Zachowaj graf zależności biblioteki źródeł obiektów tak mały, jak to możliwe. Jeśli zależy to od innych bibliotek DLL, których debugowany proces jeszcze nie załadował, uwzględnij te biblioteki DLL w tym samym podfolderze rozszerzenia.
Skonfiguruj dostawcę, aby używał niestandardowego źródła obiektów
Po spakowaniu biblioteki DLL źródła obiektu zaktualizuj DebuggerVisualizerProviderConfiguration, aby wskazywał na nią:
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);
}
Jeśli celowo unikasz odwołania do zestawu z rozszerzenia do projektu object-source, skonfiguruj VisualizerObjectSourceType przy użyciu nazwy kwalifikowanej przez zestaw zamiast typeof(...). Przykład Regex używa tego wzorca.
Praca z dużymi i złożonymi obiektami
Jeśli pobieranie wszystkich danych w jednym wywołaniu RequestDataAsync bez parametrów nie jest praktyczne, użyj polecenia RequestDataAsync<TMessage, TResponse>(TMessage, JsonSerializer?, CancellationToken) ↗ do wymiany komunikatów ze źródłem obiektu.
Ten wzorzec jest przydatny w przypadku dużych kolekcji, ponieważ każde RequestDataAsync wywołanie ma pięciosekundowy limit czasu. Typowym podejściem jest żądanie wpisów pojedynczo:
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);
}
Po stronie źródła obiektów nadpisz GetData elementem TransferData:
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 Odczytuje komunikat wysłany przez dostawcę za pośrednictwem incomingData
Gdy wizualizator wykonuje bardziej złożone wymiany komunikatów, często lepiej jest przekazać odwołanie VisualizerTarget ↗ do zdalnej kontrolki użytkownika, aby mogła asynchronicznie żądać danych podczas ładowania kontrolki lub później w odpowiedzi na interakcję użytkownika:
public override Task<IRemoteUserControl> CreateVisualizerAsync(
VisualizerTarget visualizerTarget,
CancellationToken cancellationToken)
{
return Task.FromResult<IRemoteUserControl>(new MyVisualizerUserControl(visualizerTarget));
}
Otwórz wizualizatory jako okna narzędzi
Domyślnie wizualizatory debugera otwierają się jako modalne okna dialogowe na pierwszym planie Visual Studio. Jeśli użytkownik chce kontynuować interakcję ze środowiskiem IDE, najpierw musi zamknąć okno modalne wizualizatora.
Jeśli ustawisz Style = VisualizerStyle.ToolWindow, wizualizator zostanie otwarty jako niemodalne okno narzędziowe i może pozostać otwarty przez resztę sesji debugowania. Jeśli nie ustawisz Styleparametru , wartość domyślna to ModalDialog.
public override DebuggerVisualizerProviderConfiguration DebuggerVisualizerProviderConfiguration => new(
"My visualizer",
typeof(TypeToVisualize))
{
Style = VisualizerStyle.ToolWindow
};
Gdy wizualizator otworzy się jako okno narzędzia, musi nasłuchiwać zdarzenia VisualizerTarget.Changed. Okna narzędzi nie blokują użytkownikowi wznowienia wykonywania, więc debuger zgłasza powiadomienia za każdym razem, gdy cel debugowania ulegnie zmianie. Gdy element docelowy jest niedostępny, wywołania do ObjectSource kończą się niepowodzeniem z błędem 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 jest wywoływany po utworzeniu elementu RemoteUserControl i tuż przed wyświetleniem okna narzędzia w programie Visual Studio.
ValueUpdated wskazuje, że wyrażenie zostało pomyślnie ponownie ocenione w bieżącym stanie przerwania, a interfejs użytkownika powinien zostać odświeżony.
Unavailable wskazuje, że wykonanie zostało wznowione lub że nie można było ponownie ocenić wyrażenia po zatrzymaniu wykonywania.
Aktualizowanie zwizualizowanej wartości obiektu
Jeśli VisualizerTarget.IsTargetReplaceable ↗ ma wartość true, wizualizator może zaktualizować wartość debuggee, wywołując metodę ReplaceTargetObjectAsync ↗.
await visualizerTarget.ObjectSource.ReplaceTargetObjectAsync(
newValue,
jsonSerializer: null,
cancellationToken);
Źródło obiektu wizualizatora musi przesłonić CreateReplacementObject:
public override object CreateReplacementObject(object target, Stream incomingData)
{
// Read the replacement value from incomingData.
return newValue;
}
Porady
✅ DO
- Umieść metadane rozszerzenia w
ExtensionConfiguration, a ustawienia współtworzenia wDebuggerVisualizerProviderConfiguration. - Użyj polecenia
netstandard2.0dla projektu object-source, chyba że masz określony powód, aby zastosować inny framework. - Używaj
DataContractudostępnionych typów danychDataMember. - Spakuj bibliotekę DLL źródła obiektu i wszystkie wymagane zależności razem z rozszerzeniem.
- Użyj stronicowanych
RequestDataAsyncwywołań dla dużych lub kosztownych obiektów. - Obsługuj
VisualizerTarget.Changedpodczas otwierania jako okno narzędziowe.
❌ NIE RÓB
- Nie odwołuj się do pakietów VisualStudio.Extensibility z projektu object-source.
- Nie zakładaj, że każdy typ może być serializowany przez domyślne źródło obiektów.
- Nie pozostawiaj wizualizatora okna narzędziowego nieświadomego powiadomień
Unavailable. - Nie zapomnij, że wywołania
RequestDataAsyncmogą przekroczyć limit czasu po pięciu sekundach.
🔧 Rozwiązywanie problemów
| Objaw | Co należy sprawdzić? |
|---|---|
| Wizualizator się nie pojawia | Sprawdź, czy klasa dostawcy ma [VisualStudioContribution] i czy DebuggerVisualizerProviderConfiguration wskazuje poprawny typ .NET. |
RequestDataAsync kończy się niepowodzeniem lub przekracza limit czasu |
Przenieś pobieranie dużych danych do TransferData i podziel dane na strony w wielu wywołaniach. |
| Nie znaleziono źródła obiektu niestandardowego | Upewnij się, że biblioteka DLL jest kopiowana do folderu rozszerzenia netstandard2.0 i VisualizerObjectSourceType wskazuje poprawną nazwę typu lub nazwę kwalifikowaną zestawem. |
| Wizualizator okna narzędzi powoduje wyjątek, gdy debugowanie jest kontynuowane | Obsłuż VisualizerTargetStateNotification.Unavailable i unikaj wywołań ObjectSource, dopóki obiekt docelowy nie będzie ponownie dostępny. |
Następne kroki
- Zapoznaj się z pełnym przykładem ↗ RegexMatchDebugVisualizer.
- Dowiedz się więcej o zdalnym interfejsie użytkownika.
- Zapoznaj się z Współtworzeniem i konfiguracjami.
- Porównaj modele w Wybierz odpowiedni model rozszerzalności.