贡献和配置

可以通过从某些基类派生来向 Visual Studio 公开扩展组件,并且可以通过定义某些属性并使用各种属性来配置它们。

Visual Studio 贡献

Visual Studio 扩展的目的是为 Visual Studio 提供 新功能。 这是通过扩展许多类之一来实现的,例如 CommandToolWindowExtensionPart 应用 VisualStudioContribution 属性。

本文引用 命令父 级示例扩展来说明参与和配置扩展组件的概念。

每个 VisualStudio.Extensibility 扩展插件必须至少提供一个 Extension 类:

namespace CommandParentingSample;

[VisualStudioContribution]
public class CommandParentingSampleExtension : Extension
{
    /// <inheritdoc/>
    protected override void InitializeServices(IServiceCollection serviceCollection)
    {
        base.InitializeServices(serviceCollection);
    }
}

Extension 类是扩展的第一个实例化类,允许将自己的服务添加到 IServiceCollection 要用于依赖项注入的服务。

命令父级示例为 Visual Studio 贡献另一个类:a Command

[VisualStudioContribution]
internal class SampleCommand : Command
{
    public SampleCommand()
    {
    }
    ...

扩展 VisualStudio.Extensibility SDK 提供的基类时,可以通过检查基类是否实现IVisualStudioContributionClass(同时Extension执行)Command来了解是否应使用VisualStudioContribution特性。

Visual Studio 贡献类是延迟实例化的单一实例:只创建一个实例,其创建延迟到 Visual Studio 需要与之交互(例如,用户首次调用某个 Command 实例时)。

VisualStudio.Extensibility 基础结构还允许你通过依赖项注入接收服务,作为 Visual Studio 贡献类的构造函数参数(请参阅 SDK 提供的用于注入的服务),包括添加到IServiceCollectionInitializeServices方法中的任何Extension服务。

Visual Studio 通常需要唯一标识符与贡献相关联。 在大多数情况下,VisualStudio.Extensibility 基础结构使用 Visual Studio 贡献类的全名作为贡献标识符。 例如,上述类的 Extension 标识符为 CommandParentingSample.CommandParentingSampleExtension. 你可能想要仔细选择 Visual Studio 贡献类的类型名称和命名空间,因为它们可能显示在 Visual Studio 日志和错误消息中。

配置 Visual Studio 贡献

大多数 Visual Studio 贡献类都需要或允许配置。 例如, Command 抽象类需要属性的 CommandConfiguration 实现,该属性至少指定命令的显示名称和(可选)其他属性(如其位置)。

[VisualStudioContribution]
internal class SampleCommand : Command
{
    /// <inheritdoc />
    public override CommandConfiguration CommandConfiguration => new("%CommandParentingSample.SampleCommand.DisplayName%")
    {
        Placements = new[]
        {
            // File in project context menu
            CommandPlacement.VsctParent(new Guid("{d309f791-903f-11d0-9efc-00a0c911004f}"), id: 1072, priority: 0),

            // Project context menu
            CommandPlacement.VsctParent(new Guid("{d309f791-903f-11d0-9efc-00a0c911004f}"), id:  1026, priority: 0),

            // Solution context menu
            CommandPlacement.VsctParent(new Guid("{d309f791-903f-11d0-9efc-00a0c911004f}"), id:  1043, priority: 0),
        },
    };
    ...

CommandConfiguration 是编译 时常量,这意味着在生成扩展时计算其值,并将其包含在扩展清单中(extension.json)。 Visual Studio 无需加载扩展本身即可读取扩展清单,从而提供更好的性能。

与普通属性相比,编译时常量 受到其他限制,例如,它们必须是只读的,其初始化代码不能包括对非静态成员或多语句命令性代码块的引用。 VisualStudio.Extensibility 生成工具强制实施这些限制,并生成错误消息,如下所示:

评估编译时常量 SampleCommand.CommandConfiguration 时遇到问题。 评估编译时常量值时不支持对用户定义的非静态成员的引用。

通常,扩展不应在运行时引用 编译时常量 配置属性。

由于编译时常量配置属性具有CompileTimeEvaluation属性,因此可以轻松识别其定义。

public abstract class Command : ExecutableCommandHandler, IVisualStudioContributionClass
{
    ...
    /// <summary>
    /// Gets the configuration for this command. The value of this property is evaluated at compile time
    /// when building the Visual Studio extension.
    /// </summary>
    [CompileTimeEvaluation]
    public abstract CommandConfiguration CommandConfiguration { get; }
    ...

在极少数情况下,配置属性可能是可选的。 在某些情况下,可能需要在同一类上实现多个配置属性。 在扩展 ExtensionPart 和实现多个接口时,这很常见,每个接口都需要自己的配置属性。

独立配置属性

如上所述,Visual Studio 贡献类定义一个单一实例类,该类通常公开一个或多个 编译时常量 配置属性。 配置属性值保存为扩展元数据。

某些扩展性功能要求指定未绑定到任何类的扩展元数据,并且它本身有意义,或者其他配置将引用它。 一些示例包括菜单、工具栏和文档类型定义。 这可以通过将 VisualStudioContribution 属性应用于静态只读配置属性来实现。

Visual Studio 贡献属性可以放置在任何类中。

命令父级示例通过声明类型的ToolbarConfiguration静态属性并将其标记为 VisualStudioContribution 来定义工具栏。

namespace CommandParentingSample;

internal static class ExtensionCommandConfiguration
{
    [VisualStudioContribution]
    public static ToolbarConfiguration ToolBar => new("%CommandParentingSample.ToolBar.DisplayName%")
    {
        Children = new[]
        {
            ToolbarChild.Command<SampleCommand>(),
        },
    };
}

Visual Studio 贡献属性也是 编译时常量 ,受前面讨论的相同限制的约束。

Visual Studio 贡献属性还可以引用另一个配置属性。 例如:

public static class MenuConfigurations
{
    [VisualStudioContribution]
    public static CommandGroupConfiguration MyCommandGroup => new(GroupPlacement.KnownPlacements.ExtensionsMenu)
    {
        Children = new GroupChild[]
        {
            GroupChild.Menu(MyMenu),
        },
    };

    [VisualStudioContribution]
    public static MenuConfiguration MyMenu => new("%MyMenu.DisplayName%")
    {
        Children = new[]
        {
            MenuChild.Command<MyCommand>(),
        },
    };
    ...

用于定义 Visual Studio 贡献属性的类型实现 IVisualStudioContributionProperty 接口,并在生成扩展时用 CompileTimeEvaluation 属性标记其值。

[CompileTimeEvaluation]
public sealed class DocumentTypeConfiguration : IVisualStudioContributionProperty ...

有关在运行时不引用 编译时常量 配置属性的指南也适用于 Visual Studio 贡献属性。

如果 Visual Studio 贡献属性需要唯一标识符,则 VisualStudio.Extensibility 基础结构会将其全名(包含类型全名和属性名称)用作标识符。 例如,此处讨论的工具栏配置的唯一标识符是 CommandParentingSample.ExtensionCommandConfiguration.ToolbarConfiguration