Создание визуализаторов отладчика Visual Studio

Визуализаторы отладчика — это функция Visual Studio, которая предоставляет настраиваемую визуализацию для переменных или объектов определенного типа .NET во время сеанса отладки.

Визуализаторы отладчика доступны из подсказки dataTip, которая отображается при наведении указателя мыши на переменную или из окон "Авто", "Локальные" и "Контрольные значения":

Screenshot of debugger visualizers in the watch window.

Начать

Следуйте инструкциям в разделе "Создание проекта расширения" в разделе "Начало работы".

Затем добавьте к нему расширение класса DebuggerVisualizerProvider и примените VisualStudioContribution к нему атрибут:

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

Предыдущий код определяет новый визуализатор отладчика, который применяется к объектам типа string:

  • Свойство DebuggerVisualizerProviderConfiguration определяет отображаемое имя визуализатора и поддерживаемый тип .NET.
  • Метод CreateVisualizerAsync вызывается Visual Studio, когда пользователь запрашивает отображение визуализатора отладчика для определенного значения. CreateVisualizerAsyncVisualizerTarget использует объект для получения значения для визуализации и передает его пользовательскому удаленному элементу управления (ссылка на документацию по удаленному пользовательскому интерфейсу). Затем возвращается элемент управления удаленным пользователем и отображается во всплывающем окне в Visual Studio.

Назначение нескольких типов

Свойство конфигурации позволяет визуализатору ориентироваться на несколько типов, когда это удобно. Идеальным примером этого является визуализатор Набора данных, поддерживающий визуализацию DataSetобъектов , DataTableDataViewи DataViewManager объектов. Эта возможность упрощает разработку расширений, так как аналогичные типы могут совместно использовать один и тот же пользовательский интерфейс, модели просмотра и источник объектов визуализатора.

    /// <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)
    {
        ...
    }

Источник объекта визуализатора

Источник объекта визуализатора — это класс .NET, загруженный отладчиком в процессе отладки. Визуализатор отладчика может извлекать данные из источника объекта визуализатора с помощью методов, предоставляемых VisualizerTarget.ObjectSource.

Источник объектов визуализатора по умолчанию позволяет визуализаторам отладчика получить значение объекта, которое будет визуализировано путем вызова RequestDataAsync<T>(JsonSerializer?, CancellationToken) метода. Источник объектов визуализатора по умолчанию использует Newtonsoft.Json для сериализации значения, а библиотеки VisualStudio.Extensibility также используют Newtonsoft.Json для десериализации. Кроме того, можно использовать RequestDataAsync(CancellationToken) для получения сериализованного значения в виде JToken.

Если вы хотите визуализировать тип .NET, который изначально поддерживается Newtonsoft.Json, или вы хотите визуализировать собственный тип, и вы можете сделать его сериализуемым, предыдущие инструкции достаточно для создания простого визуализатора отладчика. Ознакомьтесь с тем, чтобы поддерживать более сложные типы или использовать более сложные функции.

Использование пользовательского источника объекта визуализатора

Если тип визуализации не может быть автоматически сериализован в Newtonsoft.Json, можно создать пользовательский источник объекта визуализатора для обработки сериализации.

  • Создание проекта netstandard2.0библиотеки классов .NET. Вы можете использовать более конкретную версию платформа .NET Framework или .NET (например, net472 илиnet6.0) для сериализации объекта для визуализации.
  • Добавьте ссылку на пакет в DebuggerVisualizers версию 17.6 или более позднюю.
  • Добавьте класс, расширяющий и переопределяющий VisualizerObjectSourceGetData запись сериализованного target значения в outgoingData поток.
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
        ...
    }
}

Использование пользовательской сериализации

Метод можно использовать VisualizerObjectSource.SerializeAsJson для сериализации объекта с помощью Newtonsoft.Json в объект Stream без добавления ссылки на Newtonsoft.Json в библиотеку. SerializeAsJson Вызов будет загружаться с помощью отражения версии сборки Newtonsoft.Json в отлаживаемый процесс.

Если вам нужно ссылаться на Newtonsoft.Json, следует использовать ту же версию, на которую ссылается Microsoft.VisualStudio.Extensibility.Sdk пакет, но предпочтительнее использовать DataContract и DataMember атрибуты для поддержки сериализации объектов вместо использования типов Newtonsoft.Json.

Кроме того, вы можете реализовать собственную пользовательскую сериализацию (например, двоичную сериализацию), записываемую непосредственно в outgoingData.

Добавление библиотеки DLL источника объекта визуализатора в расширение

Измените файл расширения .csproj , добавив в ProjectReference проект исходной библиотеки объектов визуализатора, который гарантирует, что исходная библиотека объектов визуализатора создается до упаковки расширения.

Кроме того, Content добавьте элемент, включая библиотеку исходной библиотеки объектов визуализатора, netstandard2.0 в вложенную папку расширения.

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

Кроме того, можно использовать вложенные net4.6.2 папки или netcoreapp вложенные папки, если вы создали исходную библиотеку визуализатора, предназначенную для платформа .NET Framework или .NET. Вы даже можете включить все три вложенные папки с разными версиями исходной библиотеки объектов визуализатора, но лучше использовать только целевой объект netstandard2.0 .

Необходимо свести к минимуму количество зависимостей библиотеки источника объектов визуализатора. Если в исходной библиотеке объектов визуализатора есть зависимости, отличные от Microsoft.VisualStudio.DebuggerVisualizers и библиотек, которые уже гарантированно загружаются в процессе отладки, обязательно включите эти ФАЙЛЫ DLL в ту же вложенную папку, что и библиотека источника объектов визуализатора.

Обновите поставщика визуализатора отладчика, чтобы использовать источник пользовательского объекта визуализатора

Затем можно обновить DebuggerVisualizerProvider конфигурацию, чтобы ссылаться на источник объекта пользовательского визуализатора:

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

Работа с большими и сложными объектами

Если получение данных из источника объекта визуализатора невозможно выполнить с одним вызовом RequestDataAsyncбез параметров, вместо этого можно выполнить более сложный обмен сообщениями с источником объекта визуализатора, вызвав RequestDataAsync<TMessage, TResponse>(TMessage, JsonSerializer?, CancellationToken) несколько раз и отправив различные сообщения в источник объекта визуализатора. Сообщение и ответ сериализуются инфраструктурой VisualStudio.Extensibility с помощью Newtonsoft.Json. Другие переопределения RequestDataAsync позволяют использовать JToken объекты или реализовывать настраиваемую сериализацию и десериализацию.

Вы можете реализовать любой пользовательский протокол, используя различные сообщения для получения информации из источника объекта визуализатора. Наиболее распространенный вариант использования этой функции — нарушение извлечения потенциально большого объекта в несколько вызовов, чтобы избежать RequestDataAsync истечения времени ожидания.

Это пример того, как можно получить содержимое потенциально большой коллекции по одному элементу за раз:

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

Приведенный выше код использует простой индекс в качестве сообщения для RequestDataAsync вызовов. Соответствующий исходный код объекта визуализатора TransferData переопределит метод (вместо 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
        ...
    }
}

Приведенный выше источник объекта визуализатора VisualizerObjectSource.DeserializeFromJson использует метод для десериализации сообщения, отправляемого поставщиком incomingDataвизуализатора.

При реализации поставщика визуализатора отладчика, выполняющего сложное взаимодействие с источником объекта визуализатора, обычно лучше передать визуализатору RemoteUserControl такVisualizerTarget, чтобы обмен сообщениями может произойти асинхронно при загрузке элемента управления. VisualizerTarget Передача сообщений также позволяет отправлять сообщения в источник объекта визуализатора для получения данных на основе взаимодействия пользователя с пользовательским интерфейсом визуализатора.

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

Открытие визуализаторов в качестве инструментов Windows

По умолчанию все расширения визуализатора отладчика открываются как модальные диалоговые окна на переднем плане Visual Studio. Таким образом, если пользователь хочет продолжить взаимодействие с интегрированной среды разработки, визуализатор должен быть закрыт. Однако если Style свойство задано ToolWindow в DebuggerVisualizerProviderConfiguration свойстве, визуализатор будет открыт как не модальное окно средства, которое может оставаться открытым во время остального сеанса отладки. Если стиль не объявлен, будет использоваться значение 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);
    }

При каждом открытии визуализатора в качестве ToolWindowэлемента необходимо подписаться на событие VisualizerTargetStateChanged. При открытии визуализатора в качестве окна инструментов пользователь не блокирует отмену сеанса отладки. Таким образом, указанное выше событие упоминание ed будет запущено отладчиком всякий раз, когда изменяется состояние целевого объекта отладки. Авторы расширений визуализатора должны уделять особое внимание этим уведомлениям, так как целевой объект визуализатора доступен только в том случае, если сеанс отладки активен, а целевой объект отладки приостановлен. Если целевой объект визуализатора недоступен, вызовы ObjectSource методов завершаются сбоем 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");
            }
        }
    }
}

Уведомление Available будет получено после RemoteUserControl создания и непосредственно перед тем, как оно будет отображаться в новом окне инструмента визуализатора. Пока визуализатор остается открытым, другие VisualizerTargetStateNotification значения можно получать каждый раз, когда целевой объект отладки изменяет состояние. Уведомление ValueUpdated используется для указания того, что последнее выражение, открытое визуализатором, было успешно оценено, где отладчик остановился и должен обновляться пользовательским интерфейсом. С другой стороны, всякий раз, когда целевой объект отладки возобновляется или выражение не может быть повторно оценено после остановки, Unavailable уведомление будет получено.

Обновление значения визуализированного объекта

Если VisualizerTarget.IsTargetReplaceable задано значение true, визуализатор отладчика может использовать ReplaceTargetObjectAsync метод для обновления значения визуализированного объекта в процессе отладки.

Источник объекта визуализатора должен переопределить 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;
}

Ознакомьтесь с примером RegexMatchDebugVisualizer , чтобы увидеть эти методы в действии.