成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

C# 9 新特性:代碼生成器、編譯時反射

新聞 前端
今天 .NET 官方博客宣布 C# 9 Source Generators 第一個預覽版發布,這是一個用戶已經喊了快 5 年特性,今天終于發布了。

[[324576]]

前言

今天 .NET 官方博客宣布 C# 9 Source Generators 第一個預覽版發布,這是一個用戶已經喊了快 5 年特性,今天終于發布了。

簡介

Source Generators 顧名思義代碼生成器,它允許開發者在代碼編譯過程中獲取查看用戶代碼并且生成新的 C# 代碼參與編譯過程,并且可以很好的與代碼分析器集成提供 Intellisense、調試信息和報錯信息,可以用它來做代碼生成,因此也相當于是一個加強版本的編譯時反射。

使用 Source Generators,可以做到這些事情:

  • 獲取一個 Compilation 對象,這個對象表示了所有正在編譯的用戶代碼,你可以從中獲取 AST 和語義模型等信息
  • 可以向 Compilation 對象中插入新的代碼,讓編譯器連同已有的用戶代碼一起編譯

Source Generators 作為編譯過程中的一個階段執行:

編譯運行 -> [分析源代碼 -> 生成新代碼] -> 將生成的新代碼添加入編譯過程 -> 編譯繼續。

上述流程中,中括號包括的內容即為 Source Generators 所參與的階段和能做到的事情。

作用

.NET 明明具備運行時反射和動態 IL 織入功能,那這個 Source Generators 有什么用呢?

編譯時反射 - 0 運行時開銷

拿 ASP.NET Core 舉例,啟動一個 ASP.NET Core 應用時,首先會通過運行時反射來發現 Controllers、Services 等的類型定義,然后在請求管道中需要通過運行時反射獲取其構造函數信息以便于進行依賴注入。然而運行時反射開銷很大,即使緩存了類型簽名,對于剛剛啟動后的應用也無任何幫助作用,而且不利于做 AOT 編譯。

Source Generators 將可以讓 ASP.NET Core 所有的類型發現、依賴注入等在編譯時就全部完成并編譯到最終的程序集當中,最終做到 0 運行時反射使用,不僅利于 AOT 編譯,而且運行時 0 開銷。

除了上述作用之外,gRPC 等也可以利用此功能在編譯時織入代碼參與編譯,不需要再利用任何的 MSBuild Task 做代碼生成啦!

另外,甚至還可以讀取 XML、JSON 直接生成 C# 代碼參與編譯,DTO 編寫全自動化都是沒問題的。

AOT 編譯

Source Generators 的另一個作用是可以幫助消除 AOT 編譯優化的主要障礙。

許多框架和庫都大量使用反射,例如System.Text.Json、System.Text.RegularExpressions、ASP.NET Core 和 WPF 等等,它們在運行時從用戶代碼中發現類型。這些非常不利于 AOT 編譯優化,因為為了使反射能夠正常工作,必須將大量額外甚至可能不需要的類型元數據編譯到最終的原生映像當中。

有了 Source Generators 之后,只需要做編譯時代碼生成便可以避免大部分的運行時反射的使用,讓 AOT 編譯優化工具能夠更好的運行。

例子

INotifyPropertyChanged

寫過 WPF 或 UWP 的都知道,在 ViewModel 中為了使屬性變更可被發現,需要實現 INotifyPropertyChanged 接口,并且在每一個需要的屬性的 setter 處除法屬性更改事件:

  1. class MyViewModel : INotifyPropertyChanged 
  2. public event PropertyChangedEventHandler? PropertyChanged; 
  3.  
  4. private string _text; 
  5. public string Text 
  6. get => _text; 
  7. set 
  8. _text = value; 
  9. PropertyChanged?.Invoke(thisnew PropertyChangedEventArgs(nameof(Text))); 

當屬性多了之后將會非常繁瑣,先前 C# 引入了 CallerMemberName 用于簡化屬性較多時候的情況:

  1. class MyViewModel : INotifyPropertyChanged 
  2. public event PropertyChangedEventHandler? PropertyChanged; 
  3.  
  4. private string _text; 
  5. public string Text 
  6. get => _text; 
  7. set 
  8. _text = value; 
  9. OnPropertyChanged(); 
  10.  
  11. protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null
  12. PropertyChanged?.Invoke(thisnew PropertyChangedEventArgs(propertyName)); 

即,用 CallerMemberName 指示參數,在編譯時自動填充調用方的成員名稱。

但是還是不方便。

如今有了 Source Generators,我們可以在編譯時生成代碼做到這一點了。

為了實現 Source Generators,我們需要寫個實現了 ISourceGenerator 并且標注了 Generator的類型。

完整的 Source Generators 代碼如下:

  1. using System; 
  2. using System.Collections.Generic; 
  3. using System.Linq; 
  4. using System.Text; 
  5. using Microsoft.CodeAnalysis; 
  6. using Microsoft.CodeAnalysis.CSharp; 
  7. using Microsoft.CodeAnalysis.CSharp.Syntax; 
  8. using Microsoft.CodeAnalysis.Text; 
  9.  
  10. namespace MySourceGenerator 
  11. [Generator] 
  12. public class AutoNotifyGenerator : ISourceGenerator 
  13. private const string attributeText = @" 
  14. using System; 
  15. namespace AutoNotify 
  16. [AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false)] 
  17. sealed class AutoNotifyAttribute : Attribute 
  18. public AutoNotifyAttribute() 
  19. public string PropertyName { get; set; } 
  20. "; 
  21.  
  22. public void Initialize(InitializationContext context) 
  23. // 注冊一個語法接收器,會在每次生成時被創建 
  24. context.RegisterForSyntaxNotifications(() => new SyntaxReceiver()); 
  25.  
  26. public void Execute(SourceGeneratorContext context) 
  27. // 添加 Attrbite 文本 
  28. context.AddSource("AutoNotifyAttribute", SourceText.From(attributeText, Encoding.UTF8)); 
  29.  
  30. // 獲取先前的語法接收器  
  31. if (!(context.SyntaxReceiver is SyntaxReceiver receiver)) 
  32. return
  33.  
  34. // 創建處目標名稱的屬性 
  35. CSharpParseOptions options = (context.Compilation as CSharpCompilation).SyntaxTrees[0].Options as CSharpParseOptions; 
  36. Compilation compilation = context.Compilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText(SourceText.From(attributeText, Encoding.UTF8), options)); 
  37.  
  38. // 獲取新綁定的 Attribute,并獲取INotifyPropertyChanged 
  39. INamedTypeSymbol attributeSymbol = compilation.GetTypeByMetadataName("AutoNotify.AutoNotifyAttribute"); 
  40. INamedTypeSymbol notifySymbol = compilation.GetTypeByMetadataName("System.ComponentModel.INotifyPropertyChanged"); 
  41.  
  42. // 遍歷字段,只保留有 AutoNotify 標注的字段 
  43. List<IFieldSymbol> fieldSymbols = new List<IFieldSymbol>(); 
  44. foreach (FieldDeclarationSyntax field in receiver.CandidateFields) 
  45. SemanticModel model = compilation.GetSemanticModel(field.SyntaxTree); 
  46. foreach (VariableDeclaratorSyntax variable in field.Declaration.Variables) 
  47. // 獲取字段符號信息,如果有 AutoNotify 標注則保存 
  48. IFieldSymbol fieldSymbol = model.GetDeclaredSymbol(variable) as IFieldSymbol; 
  49. if (fieldSymbol.GetAttributes().Any(ad => ad.AttributeClass.Equals(attributeSymbol, SymbolEqualityComparer.Default))) 
  50. fieldSymbols.Add(fieldSymbol); 
  51.  
  52. // 按 class 對字段進行分組,并生成代碼 
  53. foreach (IGrouping<INamedTypeSymbol, IFieldSymbol> group in fieldSymbols.GroupBy(f => f.ContainingType)) 
  54. string classSource = ProcessClass(group.Key, group.ToList(), attributeSymbol, notifySymbol, context); 
  55. context.AddSource($"{group.Key.Name}_autoNotify.cs", SourceText.From(classSource, Encoding.UTF8)); 
  56.  
  57. private string ProcessClass(INamedTypeSymbol classSymbol, List<IFieldSymbol> fields, ISymbol attributeSymbol, ISymbol notifySymbol, SourceGeneratorContext context) 
  58. if (!classSymbol.ContainingSymbol.Equals(classSymbol.ContainingNamespace, SymbolEqualityComparer.Default)) 
  59. // TODO: 必須在頂層,產生診斷信息 
  60. return null
  61.  
  62. string namespaceName = classSymbol.ContainingNamespace.ToDisplayString(); 
  63.  
  64. // 開始構建要生成的代碼 
  65. StringBuilder source = new StringBuilder($@" 
  66. namespace {namespaceName} 
  67. {{ 
  68. public partial class {classSymbol.Name} : {notifySymbol.ToDisplayString()} 
  69. {{ 
  70. "); 
  71.  
  72. // 如果類型還沒有實現 INotifyPropertyChanged 則添加實現 
  73. if (!classSymbol.Interfaces.Contains(notifySymbol)) 
  74. source.Append("public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;"); 
  75.  
  76. // 生成屬性 
  77. foreach (IFieldSymbol fieldSymbol in fields) 
  78. ProcessField(source, fieldSymbol, attributeSymbol); 
  79.  
  80. source.Append("} }"); 
  81. return source.ToString(); 
  82.  
  83. private void ProcessField(StringBuilder source, IFieldSymbol fieldSymbol, ISymbol attributeSymbol) 
  84. // 獲取字段名稱 
  85. string fieldName = fieldSymbol.Name; 
  86. ITypeSymbol fieldType = fieldSymbol.Type; 
  87.  
  88. // 獲取 AutoNotify Attribute 和相關的數據 
  89. AttributeData attributeData = fieldSymbol.GetAttributes().Single(ad => ad.AttributeClass.Equals(attributeSymbol, SymbolEqualityComparer.Default)); 
  90. TypedConstant overridenNameOpt = attributeData.NamedArguments.SingleOrDefault(kvp&nbsp;=> kvp.Key == "PropertyName").Value; 
  91.  
  92. string propertyName = chooseName(fieldName, overridenNameOpt); 
  93. if (propertyName.Length == 0 || propertyName == fieldName) 
  94. //TODO: 無法處理,產生診斷信息 
  95. return
  96.  
  97. source.Append($@" 
  98. public {fieldType} {propertyName}  
  99. {{ 
  100. get  
  101. {{ 
  102. return this.{fieldName}; 
  103. }} 
  104. set 
  105. {{ 
  106. this.{fieldName} = value; 
  107. this.PropertyChanged?.Invoke(thisnew System.ComponentModel.PropertyChangedEventArgs(nameof({propertyName}))); 
  108. }} 
  109. }} 
  110. "); 
  111.  
  112. string chooseName(string fieldName, TypedConstant overridenNameOpt) 
  113. if (!overridenNameOpt.IsNull) 
  114. return overridenNameOpt.Value.ToString(); 
  115.  
  116. fieldName = fieldName.TrimStart('_'); 
  117. if (fieldName.Length == 0
  118. return string.Empty; 
  119.  
  120. if (fieldName.Length == 1
  121. return fieldName.ToUpper(); 
  122.  
  123. return fieldName.Substring(01).ToUpper() + fieldName.Substring(1); 
  124.  
  125.  
  126. // 語法接收器,將在每次生成代碼時被按需創建 
  127. class SyntaxReceiver : ISyntaxReceiver 
  128. public List<FieldDeclarationSyntax> CandidateFields { get; } = new List<FieldDeclarationSyntax>(); 
  129.  
  130. // 編譯中在訪問每個語法節點時被調用,我們可以檢查節點并保存任何對生成有用的信息 
  131. public void OnVisitSyntaxNode(SyntaxNode syntaxNode) 
  132. // 將具有至少一個 Attribute 的任何字段作為候選 
  133. if (syntaxNode is FieldDeclarationSyntax fieldDeclarationSyntax 
  134. && fieldDeclarationSyntax.AttributeLists.Count > 0
  135. CandidateFields.Add(fieldDeclarationSyntax); 

有了上述代碼生成器之后,以后我們只需要這樣寫 ViewModel 就會自動生成通知接口的事件觸發調用:

  1. public partial class MyViewModel 
  2. [AutoNotify] 
  3. private string _text = "private field text"
  4.  
  5. [AutoNotify(PropertyName = "Count")] 
  6. private int _amount = 5

上述代碼將會在編譯時自動生成以下代碼參與編譯:

  1. public partial class MyViewModel : System.ComponentModel.INotifyPropertyChanged 
  2. public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; 
  3.  
  4. public string Text 
  5. get  
  6. return this._text; 
  7. set 
  8. this._text = value; 
  9. this.PropertyChanged?.Invoke(thisnew System.ComponentModel.PropertyChangedEventArgs(nameof(Text))); 
  10.  
  11. public int Count 
  12. get  
  13. return this._amount; 
  14. set 
  15. this._amount = value; 
  16. this.PropertyChanged?.Invoke(thisnew System.ComponentModel.PropertyChangedEventArgs(nameof(Count))); 

非常方便!

使用時,將 Source Generators 部分作為一個獨立的 .NET Standard 2.0 程序集(暫時不支持 2.1),用以下方式引入到你的項目即可:

  1. <ItemGroup> 
  2. <Analyzer Include="..\MySourceGenerator\bin\$(Configuration)\netstandard2.0\MySourceGenerator.dll" /> 
  3. </ItemGroup> 
  4.  
  5. <ItemGroup> 
  6. <ProjectReference Include="..\MySourceGenerator\MySourceGenerator.csproj" /> 
  7. </ItemGroup> 

注意需要 .NET 5 preview 3 或以上版本,并指定語言版本為 preview :

  1. <PropertyGroup> 
  2. <LangVersion>preview</LangVersion> 
  3. </PropertyGroup> 

另外,Source Generators 需要引入兩個 nuget 包:

  1. <ItemGroup> 
  2. <PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="3.6.0-3.final" PrivateAssets="all" /> 
  3. <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.0.0" PrivateAssets="all" /> 
  4. </ItemGroup> 

限制

Source Generators 僅能用于訪問和生成代碼,但是不能修改已有代碼,這有一定原因是出于安全考量。

文檔

Source Generators 處于早期預覽階段,docs.microsoft.com 上暫時沒有相關文檔,關于它的文檔請訪問在 roslyn 倉庫中的文檔:

設計文檔

使用文檔

后記

目前 Source Generators 仍處于非常早期的預覽階段,API 后期還可能會有很大的改動,因此現階段不要用于生產。

另外,關于與 IDE 的集成、診斷信息、斷點調試信息等的開發也在進行中,請期待后續的 preview 版本吧。

 

責任編輯:張燕妮 來源: 博客園
相關推薦

2015-08-25 15:54:17

程序員代碼生成器

2021-07-23 11:24:54

Create Inc開源G代碼生成器

2023-05-17 16:02:00

CSS工具代碼生成器

2009-08-26 17:10:09

C# 3.5新特性

2025-03-17 03:00:00

C#性能并行處理

2022-03-10 10:48:30

PolyCoder自動代碼生成器語言

2009-08-04 09:09:51

C#反射

2023-01-06 07:52:52

代碼生成器開發

2025-03-07 00:12:39

2009-08-19 16:51:14

C# 4.0 dyna

2009-08-27 16:24:48

擴展方法C# 3.0新特性

2009-07-01 17:30:14

樣式生成器Visual Stud

2021-06-06 16:31:57

PythonPython 3.7 編程語言

2020-10-20 09:53:11

代碼IDEA生成器

2021-10-29 11:25:41

代碼編程語言Java

2012-03-30 09:31:44

WEBCSS

2021-12-10 09:45:19

生成器配置代碼

2025-03-28 01:05:13

2009-08-31 14:45:07

Visual C# 3

2009-05-26 09:28:22

C# 4.0dynamic動態類型
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 亚州精品成人 | 日日操操| 中文在线一区二区 | h视频在线免费 | 亚洲人va欧美va人人爽 | 欧美一区二区视频 | 精品福利在线 | 亚洲国产高清在线 | 九色视频网站 | 国产成人网 | 高清不卡毛片 | 日本一级淫片免费啪啪3 | 亚洲有码转帖 | 中文在线一区二区 | 免费一级毛片 | 狠狠做深爱婷婷综合一区 | 羞羞视频网站 | 全免费a级毛片免费看视频免 | 国产精品久久二区 | 国产在线播放一区二区三区 | 日本精品在线播放 | 国产精品a久久久久 | 人人鲁人人莫人人爱精品 | 欧美一级大片免费观看 | 日韩欧美在线观看视频 | 三级视频久久 | 日本免费视频在线观看 | 国产资源视频 | 免费视频一区二区 | 亚州激情 | 免费在线精品视频 | 免费视频中文字幕 | 成人在线免费视频 | 欧美一区二 | 国产一区二区免费 | 中国毛片免费 | 999国产精品视频免费 | 午夜影院在线免费观看视频 | 久久国内精品 | 欧美中文字幕在线 | 欧美高清视频一区 |