tnblog
首页
视频
资源
登录

.net Roslyn的基本使用

1984人阅读 2024/9/28 21:30 总访问:3660629 评论:0 收藏:0 手机
分类: .net后台框架

.net Roslyn的基本使用

Roslyn简介


Roslyn是C#和Visual Basic编译器的开源实现,具有用于构建代码分析工具的API表面。
Roslyn还提供可供IDE使用的语言服务,例如重构、代码修复或编辑并继续。

Roslyn分析器


Roslyn 分析器允许您使用 Roslyn 中的数据来检查代码以检测问题。分析器可以直接在编辑器中添加错误、警告或波浪线。

简单实践


首先创建一个Analyzer with Code Fix项目命名为MyRoslyn
框架我选择的4.7.2版本。


该解决方案包含4个项目:

项目名 描述
MyRoslyn 负责定义代码分析器(Analyzer)。
MyRoslyn.CodeFixes 用于修复由分析器(Analyzer)检测到的问题。
MyRoslyn.Package 用于打包你的分析器和代码修复项目为一个 NuGet 包。
MyRoslyn.Test 编写测试代码来验证你的分析器和代码修复器是否正常工作。
MyRoslyn.Vsix 将你的分析器和代码修复器集成到 Visual Studio 中,让它们能够自动在 IDE 中运行和生效。


首先我们打开MyRoslynAnalyzer代码。
查看其中的每一行意思。

  1. /// <summary>
  2. /// 这是一个 C# 的诊断分析器
  3. /// </summary>
  4. [DiagnosticAnalyzer(LanguageNames.CSharp)]
  5. public class MyRoslynAnalyzer : DiagnosticAnalyzer
  6. {
  7. // 诊断 ID,用来标识分析器,类似身份证号
  8. public const string DiagnosticId = "MyRoslyn";
  9. // LocalizableResourceString 这样可以支持多语言。
  10. // See https://github.com/dotnet/roslyn/blob/main/docs/analyzers/Localizing%20Analyzers.md for more on localization
  11. // 分析器的标题
  12. private static readonly LocalizableString Title = new LocalizableResourceString(nameof(Resources.AnalyzerTitle), Resources.ResourceManager, typeof(Resources));
  13. // 分析器发现问题时显示具体的提示内容
  14. private static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(Resources.AnalyzerMessageFormat), Resources.ResourceManager, typeof(Resources));
  15. // 对问题的详细描述,解释为什么这是个问题
  16. private static readonly LocalizableString Description = new LocalizableResourceString(nameof(Resources.AnalyzerDescription), Resources.ResourceManager, typeof(Resources));
  17. // 这个问题的分类,这里是命名问题
  18. private const string Category = "Naming";
  19. // 定义诊断规则,包括诊断ID、标题、消息格式、分类、严重性等
  20. private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(
  21. DiagnosticId, // 诊断ID
  22. Title, // 标题
  23. MessageFormat, // 提示消息
  24. Category, // 分类
  25. DiagnosticSeverity.Warning, // 严重性,这里是警告
  26. isEnabledByDefault: true, // 默认启用
  27. description: Description); // 问题描述
  28. // 这里是分析器支持的所有规则列表,这个分析器目前只有一个规则
  29. public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics {
  30. get { return ImmutableArray.Create(Rule); } // 返回诊断规则
  31. }
  32. // 分析器的初始化方法,主要是注册具体的分析动作
  33. public override void Initialize(AnalysisContext context)
  34. {
  35. // 这两行代码告诉分析器不要分析自动生成的代码,并启用并发执行
  36. context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
  37. context.EnableConcurrentExecution();
  38. // 注册一个分析符号的动作,更多信息参考链接
  39. // See https://github.com/dotnet/roslyn/blob/main/docs/analyzers/Analyzer%20Actions%20Semantics.md for more information
  40. context.RegisterSymbolAction(AnalyzeSymbol, SymbolKind.NamedType);
  41. }
  42. // 这是实际的分析逻辑
  43. private static void AnalyzeSymbol(SymbolAnalysisContext context)
  44. {
  45. // 获取当前正在被分析的符号,这里是一个命名类型(例如类或接口)
  46. var namedTypeSymbol = (INamedTypeSymbol)context.Symbol;
  47. // 找出名称中包含小写字母的命名类型
  48. if (namedTypeSymbol.Name.ToCharArray().Any(char.IsLower))
  49. {
  50. // 如果找到了,生成一个诊断信息(也就是“警告”)
  51. var diagnostic = Diagnostic.Create(Rule, namedTypeSymbol.Locations[0], namedTypeSymbol.Name);
  52. // 报告诊断信息
  53. context.ReportDiagnostic(diagnostic);
  54. }
  55. }
  56. }
  1. // 使用 ExportCodeFixProvider 特性声明这是一个代码修复提供器,并且是共享的
  2. [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(MyRoslynCodeFixProvider)), Shared]
  3. public class MyRoslynCodeFixProvider : CodeFixProvider
  4. {
  5. // 指定这个代码修复器能够修复哪些诊断 ID,此处只修复与 MyRoslynAnalyzer.DiagnosticId 相关的问题
  6. public sealed override ImmutableArray<string> FixableDiagnosticIds
  7. {
  8. get { return ImmutableArray.Create(MyRoslynAnalyzer.DiagnosticId); }
  9. }
  10. // 提供 “Fix All” 功能,允许用户一次性修复所有类似的问题
  11. public sealed override FixAllProvider GetFixAllProvider()
  12. {
  13. // 使用 WellKnownFixAllProviders.BatchFixer,它可以批量修复多个问题
  14. return WellKnownFixAllProviders.BatchFixer;
  15. }
  16. /// <summary>
  17. /// 当发现诊断问题时,注册代码修复操作
  18. /// </summary>
  19. /// <param name="context"></param>
  20. /// <returns></returns>
  21. public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
  22. {
  23. var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
  24. // 获取语法树的根节点
  25. var diagnostic = context.Diagnostics.First();
  26. // 获取当前诊断问题
  27. var diagnosticSpan = diagnostic.Location.SourceSpan;
  28. // 在语法树中找到对应的问题类型声明(比如类的声明)
  29. var declaration = root.FindToken(diagnosticSpan.Start).Parent.AncestorsAndSelf().OfType<TypeDeclarationSyntax>().First();
  30. // 注册一个修复操作,当用户点击修复时执行 MakeUppercaseAsync 方法
  31. context.RegisterCodeFix(
  32. CodeAction.Create(
  33. title: CodeFixResources.CodeFixTitle, // 修复的标题
  34. createChangedSolution: c => MakeUppercaseAsync(context.Document, declaration, c),// 生成新的解决方案
  35. equivalenceKey: nameof(CodeFixResources.CodeFixTitle)),// 区分修复操作的键
  36. diagnostic);
  37. }
  38. /// <summary>
  39. /// 这是实际执行修复的逻辑,将类名转换为大写
  40. /// </summary>
  41. /// <param name="document"></param>
  42. /// <param name="typeDecl"></param>
  43. /// <param name="cancellationToken"></param>
  44. /// <returns></returns>
  45. private async Task<Solution> MakeUppercaseAsync(Document document, TypeDeclarationSyntax typeDecl, CancellationToken cancellationToken)
  46. {
  47. // 获取类的标识符,即类名
  48. var identifierToken = typeDecl.Identifier;
  49. // 将类名转换为全大写
  50. var newName = identifierToken.Text.ToUpperInvariant();
  51. // 获取语义模型,用来理解代码中的符号和上下文
  52. var semanticModel = await document.GetSemanticModelAsync(cancellationToken);
  53. // 获取类的符号信息
  54. var typeSymbol = semanticModel.GetDeclaredSymbol(typeDecl, cancellationToken);
  55. // 获取原始的解决方案(Solution),包含项目的所有代码和引用
  56. var originalSolution = document.Project.Solution;
  57. // 获取重命名操作的设置
  58. var optionSet = originalSolution.Workspace.Options;
  59. // 调用 Renamer.RenameSymbolAsync 将类名以及所有引用的地方改为大写
  60. var newSolution = await Renamer.RenameSymbolAsync(document.Project.Solution, typeSymbol, newName, optionSet, cancellationToken).ConfigureAwait(false);
  61. // 返回包含更新后类名的解决方案
  62. return newSolution;
  63. }
  64. }


当然我们也可以自定义一个CreationAnalyzer的分析器。
我想对当有写到ImmutableArray<int>.Empty.Add(1)代码时就对其中做一些警告的提示处理。
这里我打开了另外的一个窗口,然后创建一个TempProject1项目,添加了一些简单的代码。

  1. // See https://aka.ms/new-console-template for more information
  2. using System.Collections.Immutable;
  3. Console.WriteLine("Hello, World!");
  4. int count = 0;
  5. var array = ImmutableArray.Create(1,2,3);
  6. var array2 = array.Add(4);
  7. var array3 = ImmutableArray<int>.Empty.Add(1);


然后我们打开Syntax Visualizer窗口。


分析我们我们选中的ImmutableArray<int>.Empty.Add(1)这一行。


通过分析我们会发现,表达式树解析是从右往左解析的,举例:
Add(1)—>Empty—>ImmutableArray<int>—>ImmutableArray
所以我们要锁定这一行的代码的话,首先我们会判断它有一个ArgumentList参数是大于0的,所以ArgumentList不大于0的节点的可以忽略了。
代码就这样写:

  1. // 获取当前的节点
  2. var node = (InvocationExpressionSyntax)context.Node;
  3. // 我们肯定会根据 ImmutableArray<int>.Empty.Add(1); 找到这个特点
  4. // 我们看到了ArgumentList是有(1)值的,所以小于一个参数的跳过
  5. if (node.ArgumentList.Arguments.Count != 1) return;


然后通过该节点的Expression获取到Add方法,如果我们没有Add方法的节点就可以忽略了。
但是怎么知道这个Expression的类型内,很简单:只需要选中ImmutableArray<int>.Empty.Add,它就显示出它的类型为MemberAccessExpressionSyntax.


对应的代码如下:

  1. // 无法将表达式转换成成员、方法、属性的去掉
  2. // 一般找都是从右往左去找
  3. if (!(node.Expression is MemberAccessExpressionSyntax addAccess)) return;
  4. // 判断方法名是否为Add
  5. if (addAccess.Name.Identifier.Text != "Add") return;


然后我们以此内推Empty也是这样。

  1. // 获取上一个的成员、方法、属性
  2. if (!(addAccess.Expression is MemberAccessExpressionSyntax emptyAccess)) return;
  3. // 判断是不是Empty,不是就直接返回
  4. if (emptyAccess.Name.Identifier.Text != "Empty") return;


然后到解析ImmutableArray<int>有变化了。

  1. // 判断是不是GenericNameSyntax类型的
  2. if (!(emptyAccess.Expression is GenericNameSyntax ImmutableArrayAccess)) return;
  3. // 判断是不是是否有一个泛型的类型
  4. if (ImmutableArrayAccess.TypeArgumentList.Arguments.Count != 1) return;
  5. // 判断是否是ImmutableArray
  6. if (ImmutableArrayAccess.Identifier.Text != "ImmutableArray") return;


然后我贴上完整的CreationAnalyzer代码。

  1. /// <summary>
  2. /// 这是一个 C# 的诊断分析器
  3. /// </summary>
  4. [DiagnosticAnalyzer(LanguageNames.CSharp)]
  5. public class CreationAnalyzer : DiagnosticAnalyzer
  6. {
  7. /// <summary>
  8. /// 定义诊断规则,包括诊断ID、标题、消息格式、分类、严重性等
  9. /// </summary>
  10. private static DiagnosticDescriptor descriptor =
  11. new DiagnosticDescriptor(
  12. "BadWayOfCreatingImmutableArray",
  13. "Bad Way Of Creating Immutable Array",
  14. "Bad Way Of Creating Immutable Array",
  15. "Immutable arrays",
  16. DiagnosticSeverity.Warning,
  17. isEnabledByDefault: true
  18. )
  19. ;
  20. public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics
  21. => ImmutableArray.Create(descriptor);
  22. public override void Initialize(AnalysisContext context)
  23. {
  24. context.RegisterSyntaxNodeAction(Analyze, SyntaxKind.InvocationExpression);
  25. }
  26. private void Analyze(SyntaxNodeAnalysisContext context)
  27. {
  28. // 获取当前的节点
  29. var node = (InvocationExpressionSyntax)context.Node;
  30. // 我们肯定会根据 ImmutableArray<int>.Empty.Add(1); 找到这个特点
  31. // 我们看到了ArgumentList是有(1)值的,所以小于一个参数的跳过
  32. if (node.ArgumentList.Arguments.Count != 1) return;
  33. // 无法将表达式转换成成员、方法、属性的去掉
  34. // 一般找都是从右往左去找
  35. if (!(node.Expression is MemberAccessExpressionSyntax addAccess)) return;
  36. // 判断方法名是否胃Add
  37. if (addAccess.Name.Identifier.Text != "Add") return;
  38. // 获取上一个的成员、方法、属性
  39. if (!(addAccess.Expression is MemberAccessExpressionSyntax emptyAccess)) return;
  40. // 判断是不是Empty,不是就直接返回
  41. if (emptyAccess.Name.Identifier.Text != "Empty") return;
  42. // 判断是不是GenericNameSyntax类型的
  43. if (!(emptyAccess.Expression is GenericNameSyntax ImmutableArrayAccess)) return;
  44. // 判断是不是是否有一个泛型的类型
  45. if (ImmutableArrayAccess.TypeArgumentList.Arguments.Count != 1) return;
  46. // 判断是否是ImmutableArray
  47. if (ImmutableArrayAccess.Identifier.Text != "ImmutableArray") return;
  48. // 创建提示的消息
  49. context.ReportDiagnostic(Diagnostic.Create(descriptor, node.GetLocation()));
  50. }
  51. }

项目启动测试


设置MyRoslyn.Vsix为项目启动项。


然后按F5运行。
打开我们的TempProject1项目。


我们可以看到我们创建的提示消息显示出来了。
除此之外还有它的不能以小写的类名创建,并且还给出命名的提示代码。


当然修复大小写命名的代码是MyRoslynCodeFixProvider提供的。


欢迎加群讨论技术,1群:677373950(满了,可以加,但通过不了),2群:656732739

评价

.net Roslyn 测试分析器

.net Roslyn 测试分析器[TOC] 关于项目的创建请参考:https://www.tnblog.net/hb/article/details/8473简单测试方式首先打...

tomcat 的基本使用

一、在webapps文件夹下创建一个自己的文件夹二、在自己的文件夹下面放入自己的资源资源类容三、访问(路径为8080+自己的文...

HTTPSession 的基本使用 1

一、常用方法二、获取三、使用1、创建web项目与功能类2-4、另一个功能类5-6、a、b、

Kustomize的基本使用

Kustomize的基本使用[TOC] 什么是 Kustomize?Kustomize允许您自定义原始的、无模板的 YAML 文件以用于多种用途,而原始 Y...

.net Source Generators的基本使用

.net Source Generators的基本使用[TOC] Source Generators简介Source Generators是一项C#编译功能,使C#开发人员能够在编...

剪映的基本使用。音频如何删除,音频如何删除某一部分,如何修改某张图片的播放时间

默认一张图片只有三秒钟的时间如果不够的话可以拖动修改时间。 音频如何删除?单击选中后下面就有删除选项了。 音频如何...

net core 使用 EF Code First

下面这些内容很老了看这篇:https://www.tnblog.net/aojiancc2/article/details/5365 项目使用多层,把数据库访问...

cAPS.net 保存base64位格式的图片

publicvoidUpload() { //取出图片对应的base64位字符 stringimgBase=Request[&quot;imgBase&quot;]; //c#里边的base6...

Quartz.net实例动态改变周期调度。misfire、Cron

Quartz:Java编写的开源的任务调度作业框架 类似Timer之类定时执行的功能,但是更强大Quartz.NET:是把Quartz转成C# NuGet...

.net Windows服务发布、安装、卸载、监听脚本。服务调试

一、脚本 为方便不用每次都去写安装卸载的脚本1.安装脚本@echooff @echo开始安装【服务】 %SystemRoot%\Microsoft.NET\Fr...

c、VB.net中全角半角转换方法

///&lt;summary&gt; ///转全角的函数(SBCcase) ///&lt;/summary&gt; ///&lt;paramname=&quot;input&quot;&gt;任意字符串...

.net mvc分部页,.net core分部页

.net分部页的三种方式第一种:@Html.Partial(&quot;_分部页&quot;)第二种:@{ Html.RenderPartial(&quot;分部页&quot;);}...

.net实现QQ邮箱发送邮件功能

1、微软已经帮我们封装好了发送邮件的类MailMessage,MailMessage类构造一些邮件信息,然后通过SmtpClient进行邮件发送。Mai...

StackExchange.Redis操作redis(net core支持)

官方git开源地址https://github.com/StackExchange/StackExchange.Redis官方文档在docs里边都是官方的文档通过nuget命令下...

windows 自带的netsh进行端口映射

使用netsh 把本地任意ip的25566端口 映射到192.168.81.234的25565端口netshinterfaceportproxyaddv4tov4listenaddress=0.0....
这一世以无限游戏为使命!
排名
3
文章
317
粉丝
22
评论
14
docker中Sware集群与service
尘叶心繁 : 想学呀!我教你呀
一个bug让程序员走上法庭 索赔金额达400亿日元
叼着奶瓶逛酒吧 : 所以说做程序员也要懂点法律知识
.net core 塑形资源
剑轩 : 收藏收藏
映射AutoMapper
剑轩 : 好是好,这个对效率影响大不大哇,效率高不高
ASP.NET Core 服务注册生命周期
剑轩 : http://www.tnblog.net/aojiancc2/article/details/167
ICP备案 :渝ICP备18016597号-1
网站信息:2018-2025TNBLOG.NET
技术交流:群号656732739
联系我们:contact@tnblog.net
公网安备:50010702506256
欢迎加群交流技术