Metalama简介5.配合VisualStudio自定义重构或快速操作功能






Visual Studio中有提供快速操作(小灯泡)功能

image

以及重构(小刷子)功能

image

使用它们可以快速进行一些快捷的针对代码的操作,如提取接口、添加实现、自动属性、快速重构、删除引用等。
除官方提供的功能外我们还可以使用很多第三方插件来支持更多地功能。

Metalama可以通过编写代码的形式,让我们为指定的代码添加重构快速操作的功能。

自定义一个ToString的实时模板

很多图形编程或游戏编程中,我们会用到各种自定义类如矩阵、复数、坐标系等,为了方便Debug,我们通常会为这些类增加一个ToString方法的重写。

例如

internal class Program
{
    private static void Main()
    {
        var point = new Point { X = 5, Y = 3};
        Console.WriteLine($"point = {point}");
    }
}
internal class Point
{
    public double X;
    public double Y;
    public override string ToString()
    {
        return $"({X}, {Y})";
    }
}

如果我们不想手写这个ToString方法,而想让VS直接为它生成。
则我们可以使用Metalama定义一个LiveTemplate,这样就可以在VS的工具中使用它了。

[LiveTemplate] // 表示当前Aspect为VS添加LiveTempate
internal class ToStringAttribute : TypeAspect
{
    [Introduce(WhenExists = OverrideStrategy.Override, Name = "ToString")]
    public string IntroducedToString()
    {
        var stringBuilder = new InterpolatedStringBuilder();
        stringBuilder.AddText("{ ");
        stringBuilder.AddText(meta.Target.Type.Name);
        stringBuilder.AddText(" ");

        var fields = meta.Target.Type.FieldsAndProperties.Where(f => !f.IsStatic).ToList();

        var i = meta.CompileTime(0);

        foreach (var field in fields)
        {
            if (i > 0)
            {
                stringBuilder.AddText(", ");
            }

            stringBuilder.AddText(field.Name);
            stringBuilder.AddText("=");
            stringBuilder.AddExpression(field.Invokers.Final.GetValue(meta.This));

            i++;
        }

        stringBuilder.AddText(" }");

        return stringBuilder.ToValue();
    }
}

这样在,下列代码中使用重构功能,即可看到Metalama给的实时代码提示。

internal class Point
{
    public double X;
    public double Y;
}

image

使用Metalama添加一个VisualStudio的快速操作

我们最终的目的如下,对于标注了[Tostring]的类,增加一个将[ToString]切换至手动实现的功能点击后可实现自动添加一个ToString:
image

这需要我们在Aspect``ToStringAttribute中添加一个提示:

public class ToStringAttribute : TypeAspect
{
    public override void BuildAspect(IAspectBuilder builder)
    {
        base.BuildAspect(builder);
        // 添加一个建议手动实现的重构提示
        if (builder.AspectInstance.Predecessors[0].Instance is IAttribute attribute)
        {
            builder.Diagnostics.Suggest(
                new CodeFix("将 [ToString] 切换至手动实现", codeFixBuilder => this.ImplementManually(codeFixBuilder, builder.Target)),
                builder.Target);
        }
    }

    /// 
    /// 当点击手动实现时的操作
    /// 
    private async Task ImplementManually(ICodeActionBuilder builder, INamedType targetType)
    {
        await builder.ApplyAspectAsync(targetType, this);
        await builder.RemoveAttributesAsync(targetType, typeof(ToStringAttribute));
    }

    [Introduce(WhenExists = OverrideStrategy.Override, Name = "ToString")]
    public string IntroducedToString()
    {
        // 获取非静态字段
        var fields = meta.Target.Type.FieldsAndProperties.Where(f => !f.IsStatic).ToList();

        // 构建一个$""字符串
        var stringBuilder = new InterpolatedStringBuilder();
        stringBuilder.AddText("{ ");
        stringBuilder.AddText(meta.Target.Type.Name);
        stringBuilder.AddText(" ");

        var i = meta.CompileTime(0);

        foreach (var field in fields)
        {
            if (i > 0)
            {
                stringBuilder.AddText(", ");
            }

            stringBuilder.AddText(field.Name);
            stringBuilder.AddText("=");
            stringBuilder.AddExpression(field.Invokers.Final.GetValue(meta.This));

            i++;
        }

        stringBuilder.AddText(" }");
        return stringBuilder.ToValue();
    }
}

这样就可以对于已经添加了[ToString]的类实现以上功能

[ToString]
internal class Point // 在此处触发 Ctrl+.或右键
{
    public double X;
    public double Y;
}

引用

本章源代码:https://github.com/chsword/metalama-demo
Metalama官方文档: https://doc.metalama.net/
Metalama Nuget包: https://www.nuget.org/packages/Metalama.Framework/0.5.13-preview