宽屏蓝色企业网站源码,章丘营销型网站设计公司,wordpress升级,西安广告设计培训Microsoft在最新的C#版本中引入了Source Generator。这是一项新功能#xff0c;可以让我们在代码编译时生成源代码。在本文中#xff0c;我将介绍四种C#中的代码生成方式#xff0c;以简化我们的日常工作。然后#xff0c;您可以视情况选择正确的方法。在 .NET 中#xff… Microsoft在最新的C#版本中引入了Source Generator。这是一项新功能可以让我们在代码编译时生成源代码。在本文中我将介绍四种C#中的代码生成方式以简化我们的日常工作。然后您可以视情况选择正确的方法。在 .NET 中我们有以下几种方法来帮助我们生成代码Code snippets.Reflection.T4 Template.[New] Source Generators in .NET 5.应该还有更多但本文将主要覆盖这四种方式。您可以参考我发布在GitHub上的demo: https://github.com/yanxiaodi/MyCodeSamples/tree/main/CodeGeneratorDemo. 让我们开始吧Code snippets Code snippets 是可重用的代码块可以使用热键组合将其插入我们的代码文件中。例如如果在Visual Studio中键入prop然后按TabVS将在您的类中自动生成一个属性然后您可以轻松地替换属性名称。VS已经为我们提供了大量的内置的代码片段如propifwhilefortry您可以在这里找到所有的默认代码片段列表C# Code Snippets[1]。Code snippets 的好处是您可以替换参数。例如当我们将MVVM模式用于UWP / Xamarin / WPF应用程序时经常需要在实现INotifyPropertyChanged[2]接口的类中创建属性。如果您使用MvvmCross框架它可能看起来像这样private ObservableCollectionComment _commentList;
public ObservableCollectionComment CommentList
{get _commentList;set SetProperty(ref _commentList, value);
}
我们不想复制/粘贴然后更改变量名所以我创建了一个 Code snippet 来简化工作。创建一个名为myMvvm.snippet的新文件然后复制并粘贴以下代码?xml version1.0 encodingutf-8?
CodeSnippets xmlnshttp://schemas.microsoft.com/VisualStudio/2005/CodeSnippetCodeSnippet Format1.0.0HeaderSnippetTypesSnippetTypeExpansion/SnippetType/SnippetTypesTitleMvvmCross property/TitleAuthorXiaodi Yan/AuthorShortcutmvxprop/ShortcutDescriptionA property in a ViewModel in the Xamarin project with MvvmCross./Description/HeaderSnippetDeclarationsLiteralIDProperty/IDToolTipProperty name/ToolTipDefaultProperty/Default/LiteralObjectIDtype/IDToolTipProperty type/ToolTipDefaultstring/Default/ObjectLiteralIDpProperty/IDToolTipPrivate property name/ToolTipDefaultproperty/Default/Literal/DeclarationsCode Languagecsharp![CDATA[#region $Property$;private $type$ _$pProperty$;public $type$ $Property${get _$pProperty$;set SetProperty(ref _$pProperty$, value);}#endregion]]/Code/Snippet/CodeSnippet
/CodeSnippets
在此 Code snippet 中我们使用Shortcut指定快捷方式mvxprop并使用Declarations声明一些参数。例如我们声明了一个名为的参数Property然后使用$Property将其插入到代码段中。您可以通过VS Tools 菜单中的Code Snippets Manager导入此 Code snippet或按Ctrl KCtrl B。现在您可以键入mvxprop并按TabVS可以为您创建属性-您只需手动替换属性名称即可。更多信息请参考Walkthrough: Create a code snippet[3]Code snippet functions[4]How to: Distribute code snippets[5]Code snippets 适合重复使用以插入整个类或方法或属性。您还可以将 Code snippets 分发给其他用户。当我们创建新文件或 Class 或 Method 时这很有用。但是如果要在完成后更新生成的代码则必须删除现有代码然后重新创建它。基本上它可以节省无聊的复制/粘贴时间但仅此而已。Reflection Reflection反射广泛用于许多.NET框架和库中例如ASP.NET Core[6]Entity Framework Core[7]等。它可以提供类型的[8]对象该对象描述程序集模块和类型以便您可以动态创建类型的实例从现有对象获取类型然后调用其方法或访问其字段和属性。当我们构建.NET应用程序时它将生成程序集-例如.dll文件。这些程序集包含我们的模块其中包含某些类型。类型包含成员。Reflection 能够获取这些信息。因此我们可以动态加载新的.dll文件并调用它们的方法或事件而无需编辑代码。动态表示它可以在运行时运行。换句话说当我们编译应用程序时.NET应用程序直到运行时才知道我们需要使用什么类型。通过这种方式我们可以创建一个客户端该客户端可以根据我们的规则动态执行其他程序集中的方法。如果我们遵循该规则更新其他程序集中的类则不需要更新客户端代码。让我们查看以下示例。您可以在我的示例项目中找到它。我们在CodeGeneratorDemo.ReflectionDemo.Core项目中有一个ISpeaker接口如下所示namespace CodeGeneratorDemo.ReflectionDemo.Core
{public interface ISpeaker{string SayHello();}
}
创建两个实现类ChineseSpeaker:namespace CodeGeneratorDemo.ReflectionDemo.Core
{public class ChineseSpeaker : ISpeaker{public string Name this.GetType().ToString();public string SayHello(){return Nihao;}}
}
以及 EnglishSpeaker:namespace CodeGeneratorDemo.ReflectionDemo.Core
{public class EnglishSpeaker : ISpeaker{public string Name this.GetType().ToString();public string SayHello(){return Hello!;}}
}
现在我们可以使用 Reflection 来查找ISpeaker接口的所有实现并调用其方法或属性。在CodeGeneratorDemo.ReflectionDemo项目中创建一个名为ReflectionHelper的新文件using CodeGeneratorDemo.ReflectionDemo.Core;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;namespace CodeGeneratorDemo.ReflectionDemo
{public class ReflectionHelper{public static ListType GetAvailableSpeakers(){// You can also use AppDomain.CurrentDomain.GetAssemblies() to load all assemblies in the current domain.// Get the specified assembly.var assembly Assembly.LoadFrom(Path.Combine(Directory.GetCurrentDirectory(), CodeGeneratorDemo.ReflectionDemo.Core.dll));// Find all the types in the assembly.var types assembly.GetTypes();// Apply the filter to find the implementations of ISayHello interface.var result types.Where(x x.IsClass typeof(ISpeaker).IsAssignableFrom(x)).ToList();// Or you can use types.Where(x x.IsClass x.GetInterfaces().Contains(typeof(ISpeaker))).ToList();return result;}}
}
在此类中我们加载包含所需类型的指定dll文件。然后我们可以使用 Reflection 并应用LINQ查询来找到所有ISpeaker接口的实现。在CodeGeneratorDemo.Client项目中我们可以输出每个Speaker的Name属性和调用SayHello方法private static void ReflectionSample()
{Console.WriteLine(Here is the Reflection sample:);// Find all the speakers in the current domainvar availableSpeakers ReflectionHelper.GetAvailableSpeakers();foreach (var availableSpeaker in availableSpeakers){// Create the instance of the typevar speaker Activator.CreateInstance(availableSpeaker);// Get the property info of the given property namePropertyInfo namePropertyInfo availableSpeaker.GetProperty(Name);// Then you can get the value of the propertyvar name namePropertyInfo?.GetValue(speaker)?.ToString();Console.WriteLine($I am {name});// Invoke the method of the instanceConsole.WriteLine(availableSpeaker.InvokeMember(SayHello, BindingFlags.InvokeMethod, null, speaker, null));}Console.WriteLine();
}
运行该程序您将看到以下输出Here is the Reflection sample:
I am CodeGeneratorDemo.ReflectionDemo.Core.ChineseSpeaker
Nihao
I am CodeGeneratorDemo.ReflectionDemo.Core.EnglishSpeaker
Hello!
如果我们需要添加其他语言的其他Speaker只需在同一项目中添加实现类。.NET Reflection 可以自动找出所有必需的类并正确调用方法。当我们创建插件类型的应用程序时它非常有用。首先我们创建接口并通过反射从客户端调用方法。然后我们可以在客户端界面之后创建插件这些插件可以作为* .dll文件动态加载并执行。另一种情况是框架开发。作为框架开发人员您将无法知道用户将创建哪些实现因此只能使用 Reflection 来创建这些实例。例如在某些MVVM框架中如果按照约定创建类如xxxViewModel该框架可以找到所有 ViewModel 并使用 Reflection 自动加载它们。通常当人们谈论反射时主要关注的是性能。因为它在运行时运行所以从理论上讲它比普通应用程序要慢一点。但是它在许多情况下都非常灵活尤其是在开发框架的情况下。如果可以接受程序花费几秒钟或仅几百毫秒来加载程序集则使用Reflection是没有问题的。使用Reflection的所需的主要名称空间是System.Reflection[9]和System.Type[10]。您可能还需要了解以下术语Assembly[11]Module[12]ConstructorInfo[13]MethodInfo[14]FieldInfo[15]EventInfo[16]PropertyInfo[17]ParameterInfo[18]CustomAttributeData[19]更多信息请参考以下文档Reflection in .NET[20]Viewing Type Information[21]Dynamically Loading and Using Types[22]T4 Template T4 Text Template是文本块和可以生成文本文件的控制逻辑的混合体。T4表示text template transformation。您可以使用它在Visual Studio 中为 C# 和 Visual Basic 生成文件。但是生成的文件本身可以是任何类型的文本例如* .txt文件HTML文件或任何语言的程序源代码。您可以使用C#代码或VB来控制模板中的逻辑。几年前我曾经使用NuGet包EntityFramework Reverse POCO Generator为EntityFramework生成POCO模型。它由T4 Template 实现。我只需要更新T4 Template 中的数据库连接字符串并保存它然后T4 Template 就可以读取数据库信息并自动创建所有模型和方法。T4 Template 有两种运行时和设计时。区别在于运行时T4 Template在应用程序中执行以生成文本字符串。它将创建一个包含TransformText()方法的 .cs类。即使目标计算机未安装Visual Studio也可以调用此方法来生成字符串。与此不同的是对设计时T4 Template来说当您在Visual Studio中保存模板时会生成原始源代码或文本文件。如果要使用运行时T4 Template则需要将文件的Custom Tool 属性设置为TextTemplatingFilePreprocessor。对于设计时T4 TemplateCustom Tool属性应设置为TextTemplatingFileGenerator。您可以在CodeGeneratorDemo.T4TemplateDemo项目中找到示例包含两个T4 TemplateRunTimeTextTemplateDemo.tt和DesignTimeTextTemplateDemo.tt。运行时 T4 Template要正确生成项目您需要安装System.CodeDom NuGet软件包。打开RunTimeTextTemplateDemo.tt文件对HTML代码进行一些更改然后将其保存。您将看到T4 Template 自动更新生成的文件RunTimeTextTemplateDemo.cs。其中包含一个可以在客户端代码中调用的TransformText()方法。# template languageC# #
# assembly nameSystem.Core #
# import namespaceSystem.Linq #
# import namespaceSystem.Text #
# import namespaceSystem.Collections.Generic #
htmlbody
h1Sales for Previous Month/h2
table# for (int i 1; i 10; i){ #trtdTest name # i # /tdtdTest value # i * i # /td /tr# } #/table
This report is Company Confidential.
/body/html
每次保存模板时它将更新生成的文件。在客户端代码中我们可以这样调用var page new RunTimeTextTemplateDemo();
Console.WriteLine(page.TransformText());
您将在控制台中看到生成的HTML代码。设计时 T4 Template设计时模板只能在开发程序时在Visual Studio中使用。它会生成原始文本文件-可以是.cs.html或.txt或其他任意格式的文本文件。通常您将需要定义一个model可以是文本文件XML或JSON或csv或其他或数据库然后模板从模型中读取数据并生成一些源代码。这是一个例子# template debugfalse hostspecifictrue languageC# #
# assembly nameSystem.Core #
# import namespaceSystem.Linq #
# import namespaceSystem.Text #
# import namespaceSystem.Collections.Generic #
# import namespaceSystem.IO #
# output extension.cs #using System;
using System.Threading.Tasks;namespace CodeGeneratorDemo.T4TemplateDemo.DesignTimeTextTemplateDemo
{
# var models new Liststring();// You can read the data from any source you have.string path Path.Combine(Path.GetDirectoryName(this.Host.TemplateFile), dataSource.txt);if(File.Exists(path)){models File.ReadAllText(path).Split(,).ToList();}foreach (var model in models){#public partial class #model#{public Guid Id { get; set; }public #model#(Guid id){Id id;}}public partial class #model#Service{public Task#model# Get#model#(Guid id){return Task.FromResult(new #model#(id));}}
#}
#
}
保存模板时T4 Template 可以为每个类生成模型和服务。如何创建T4 Template从上面的示例中可以看到T4 Template由以下部分组成指令-控制模板处理方式的元素。文本块-直接复制到输出的原始文本。控制块-将变量值插入文本中并控制文本的有条件或重复部分的程序代码。例如您可以使用以下指令指定输出文件格式# output extension.txt #
您也可以使用C#代码控制逻辑。例如检查以下代码#for(int i 0; i 4; i){
#
Hello!
#}
#
它将输出Hello四次。在此示例中Hello是一个文本块而该for语句只是C#代码。要使用变量可以使用表达式控制块。只需使用# ... #输出变量如下所示#string message Hello;for(int i 0; i 4; i){
##message#
#}
#
它将输出Hello四次。T4模板的强大功能是您可以导入程序集并使用所需的大多数.NET库例如# assembly nameSystem.Core #
# import namespaceSystem.Linq #
# import namespaceSystem.Text #
# import namespaceSystem.Collections.Generic #
# import namespaceSystem.IO #
请注意您需要将它们放置在原始文本和控制块之前。您甚至可以在控制块中使用反射。有了这些功能我们可以为某些情况编写非常有用的模板。调试 T4 Template像普通的C#程序一样我们可以通过设置断点来调试T4 Template。要调试设计时T4 Template请右键单击该模板然后从Solution Explorer中的文件菜单中选择Debug T4 template。要调试运行时T4 Template只需调试项目因为它会在程序编译时运行。T4 Template 编辑器默认情况下Visual Studio不支持语法着色和智能感知等。幸运的是我们有一些VS扩展来提高工作效率例如DevArt T4 Editor[23]。您可以在VS扩展市场中搜索T4 Template您将找到更多。我们不会在本文中介绍T4模板的所有详细信息。有关更多信息请阅读以下文档Code Generation and T4 Text Templates[24]Walkthrough: Generate Code by using Text Templates[25]Run-Time Text Generation with T4 Text Templates[26]T4 Text Template Directives[27]Text Template Control Blocks[28]Guidelines for Writing T4 Text Templates[29]Source Generators in .NET 5 要开始使用Source Generators您需要安装最新的.NET 5 SDK[30]。什么是 Source Generator它是如何工作的根据微软的定义A Source Generator is a piece of code that runs during compilation and can inspect your program to produce additional files that are compiled together with the rest of your code.让我们回顾一下Reflection的工作原理。如前所述在构建应用程序时Reflection代码直到应用程序运行时才知道它将使用什么类型。这就是为什么人们抱怨Reflection的性能。如果在应用启动时要加载很多程序集则可能会对性能产生轻微的影响。这个问题很难解决因为这是Reflection的弊端-您可以从开发中受益但是您必须接受它的缺点。Source Generators可用于解决性能问题-至少提高性能是其重要目标之一。Source Generators可以分析当前源代码并在代码编译过程中生成一些将与当前源代码一起编译的代码-换句话说当应用程序完成编译时它已经完全知道它将使用哪种类型。这是改进的关键。这是Microsoft提供的的Source Generators的示意图我们需要知道的一件事是源生成器只能向代码中添加内容而不能更改任何现有代码。让我们来看一个例子。第一个 Source Generator 实例Source Generate 需要实现 Microsoft.CodeAnalysis.ISourceGenerator接口namespace Microsoft.CodeAnalysis
{public interface ISourceGenerator{void Initialize(GeneratorInitializationContext context);void Execute(GeneratorExecutionContext context);}
}
创建一个名为CodeGeneratorDemo.SourceGeneratorDemo的新.NET Standard 2.0 Class项目。安装以下两个NuGet软件包Microsoft.CodeAnalysis.CSharp v3.8Microsoft.CodeAnalysis.Analyzers v3.3我们还需要将语言版本指定为previewPropertyGroupTargetFrameworknetstandard2.0/TargetFrameworkLangVersionpreview/LangVersion
/PropertyGroup
从技术上讲源生成器还不是C#的正式功能现在仍在预览中。因此我们需要明确指定preview版本。然后在项目中创建一个SpeakersSourceGenerator.cs文件。更新内容如下所示using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
using System.Collections.Generic;
using System.Text;namespace CodeGeneratorDemo.SourceGeneratorDemo
{[Generator]public class SpeakersSourceGenerator : ISourceGenerator{public void Initialize(GeneratorInitializationContext context){// Not needed for this sample}public void Execute(GeneratorExecutionContext context){// begin creating the source well inject into the users compilationvar sourceBuilder new StringBuilder(
using System;
namespace CodeGeneratorDemo.SourceGeneratorDemo
{public static class SpeakerHelper{public static void SayHello() {Console.WriteLine(Hello from generated code!);
);sourceBuilder.Append(}}
});// inject the created source into the users compilationcontext.AddSource(speakersSourceGenerator, SourceText.From(sourceBuilder.ToString(), Encoding.UTF8));}}
}
该SpeakersSourceGenerator类实现了ISourceGenerator接口并具有Generator属性。程序编译时它将找到Source Generators并生成我们需要的代码。在此示例中我仅创建了一个名为SpeakerHelper的类包含一个SayHello()方法。如果我们正确生成了代码它将在控制台中输出消息。接下来将引用添加到CodeGeneratorDemo.Client项目。请注意您需要像这样更新项目文件Project SdkMicrosoft.NET.SdkPropertyGroupOutputTypeExe/OutputTypeTargetFrameworknet5.0/TargetFrameworkLangVersionpreview/LangVersion/PropertyGroupItemGroupProjectReference Include..\CodeGeneratorDemo.SourceGeneratorDemo\CodeGeneratorDemo.SourceGeneratorDemo.csproj OutputItemTypeAnalyzerReferenceOutputAssemblyfalse//ItemGroup
/Project
您还需要指定语言版本。另外由于我们没有将项目引用为普通的dll文件因此我们需要更新OutputItemType和ReferenceOutputAssembly的值如上所示。在客户端代码中添加代码private static void SourceGeneratorSample()
{CodeGeneratorDemo.SourceGeneratorDemo.SpeakerHelper.SayHello();
}
您可能会看到VS报错找不到CodeGeneratorDemo.SourceGeneratorDemo.SpeakerHelper因为我们的代码中还没有这个类。Source Generators的工具仍在预览中因此我们需要构建CodeGeneratorDemo.SourceGeneratorDemo项目并关闭VS然后重新启动它。然后您会发现VS可以支持智能感知了。当我们构建它时Source Generators实际上会生成SpeakerHelper类。现在运行客户端应用程序我们可以看到输出来自生成的代码Hello from generated code!
因此这个过程是当我们构建项目时将调用Source Generators来生成一些可以与原始源代码一起编译的代码。这样就不会出现性能问题因为它发生在编译中。当应用程序启动时生成的代码已与其他源代码一起编译。根据我的经验有时VS无法识别生成的方法或类只要构建正确运行即可。如果在客户端代码中按一下F12以检查SayHello()方法您将看到生成的文件该文件显示此文件无法编辑您可能很好奇文件在哪里。如果要查看实际生成的文件可以将以下部分添加到CodeGeneratorDemo.SourceGeneratorDemo项目和CodeGeneratorDemo.Client项目中EmitCompilerGeneratedFilestrue/EmitCompilerGeneratedFiles
CompilerGeneratedFilesOutputPath$(BaseIntermediateOutputPath)\GeneratedFiles/CompilerGeneratedFilesOutputPath
然后您可以在obj/GeneratedFiles文件夹中找到该文件。如果未指定CompilerGeneratedFilesOutputPath属性则该属性应位于obj/SourceGeneratorFiles文件夹中。这只是一个非常简单的示例展示了如何在运行时之前生成代码。接下来让我们看另一个更复杂的示例。在编译时生成Attribute考虑以下场景当我们使用依赖注入时通常我们需要手动注册实例。对于此演示我将创建一个Attribute[31]来装饰需要注册的类。我们可以使用Reflection来检索这些属性以找到特定的类但是操作可能很昂贵。使用Source Generators我们可以在编译时生成代码以在运行时之前对其进行注册。创建一个新类AutoRegisterSourceGenerator如下所示[Generator]
public class AutoRegisterSourceGenerator : ISourceGenerator
{public void Initialize(GeneratorInitializationContext context){// TODO}public void Execute(GeneratorExecutionContext context){// TODO}
}
接下来让我们创建Attribute。我们可以创建一个实际的类但是为了进行演示我将使用Source Generator生成它。将以下代码添加到AutoRegisterSourceGeneratorprivate const string AttributeText
using System;
namespace CodeGeneratorDemo.SourceGeneratorDemo
{[AttributeUsage(AttributeTargets.Class, Inherited false, AllowMultiple false)]sealed class AutoRegisterAttribute : Attribute{public AutoRegisterAttribute(){}}
};
这只是一个字符串。接下来更新Execute方法以将字符串添加到源代码中public void Execute(GeneratorExecutionContext context)
{context.AddSource(AutoRegisterAttribute, SourceText.From(AttributeText, Encoding.UTF8));
}
当我们构建项目时它将生成AutoRegisterAttribute。下一步是创建一些接口namespace CodeGeneratorDemo.Client.Core
{public interface IOrderService{}public interface IProductService{}
}
还有一些实现类例如OrderService和ProductService由AutoRegister属性装饰using System;
using CodeGeneratorDemo.SourceGeneratorDemo;namespace CodeGeneratorDemo.Client.Core
{[AutoRegister]public class OrderService : IOrderService{public OrderService(){Console.WriteLine(${this.GetType()} constructed.);}}[AutoRegister]public class ProductService : IProductService{public ProductService(){Console.WriteLine(${this.GetType()} constructed.);}}
}
目前我们的代码中没有AutoRegister。因此您将看到VS报错。没关系因为稍后Source Generator会生成它。我们将调用另一个类DiContainerMocker来模拟DI容器using System;
namespace CodeGeneratorDemo.Client.Core
{public static class DiContainerMocker{public static void RegisterServiceTInterface, TImplementation(TImplementation service){Console.WriteLine(${service.GetType()} has been registered for {typeof(TInterface)}.);}}
}
Source Generators依赖于Roslyn[32]。它可以检查要编译的数据。我们可以使用称为SyntaxReceivers的对象来访问SyntaxTrees然后根据这些信息进行迭代其中的SyntaxNodes然后生成代码。创建一个名为MySyntaxReceiver的新类该类实现了ISyntaxReceiver接口public class MySyntaxReceiver : ISyntaxReceiver
{public ListClassDeclarationSyntax CandidateClasses { get; } new ListClassDeclarationSyntax();/// summary/// Called for every syntax node in the compilation, we can inspect the nodes and save any information useful for generation/// /summarypublic void OnVisitSyntaxNode(SyntaxNode syntaxNode){// any method with at least one attribute is a candidate for property generationif (syntaxNode is ClassDeclarationSyntax classDeclarationSyntax classDeclarationSyntax.AttributeLists.Count 0){CandidateClasses.Add(classDeclarationSyntax);}}
}
在这个类中我们将检查每个SyntaxNode。如果它是一个Class并且具有Attribute那么我们将其添加到列表中。接下来我们需要在Source Generator的Initialize方法中注册MySyntaxReceiverpublic void Initialize(GeneratorInitializationContext context)
{context.RegisterForSyntaxNotifications(() new MySyntaxReceiver());
}
现在来完成我们的Source Generator。我的想法是我们将依次检查每个SyntaxNode如果它是一个Class并具有AutoRegister属性那么就生成注册代码。通过以下代码更新Execute方法 public void Execute(GeneratorExecutionContext context){context.AddSource(AutoRegisterAttribute, SourceText.From(AttributeText, Encoding.UTF8));if (!(context.SyntaxReceiver is MySyntaxReceiver receiver)){return;}CSharpParseOptions options (context.Compilation as CSharpCompilation).SyntaxTrees[0].Options as CSharpParseOptions;SyntaxTree attributeSyntaxTree CSharpSyntaxTree.ParseText(SourceText.From(AttributeText, Encoding.UTF8), options);Compilation compilation context.Compilation.AddSyntaxTrees(attributeSyntaxTree);StringBuilder stringBuilder new StringBuilder();stringBuilder.Append(
using System;
using CodeGeneratorDemo.Client.Core;
namespace CodeGeneratorDemo.SourceGeneratorDemo
{public class RegisterHelper{public static void RegisterServices(){
);// Get all the classes with the AutoRegisterAttributeINamedTypeSymbol attributeSymbol compilation.GetTypeByMetadataName(CodeGeneratorDemo.SourceGeneratorDemo.AutoRegisterAttribute);foreach (var candidateClass in receiver.CandidateClasses){SemanticModel model compilation.GetSemanticModel(candidateClass.SyntaxTree);if (model.GetDeclaredSymbol(candidateClass) is ITypeSymbol typeSymbol typeSymbol.GetAttributes().Any(x x.AttributeClass.Equals(attributeSymbol, SymbolEqualityComparer.Default))){stringBuilder.Append($DiContainerMocker.RegisterServiceI{candidateClass.Identifier.Text}, {candidateClass.Identifier.Text}(new {candidateClass.Identifier.Text}()););}}stringBuilder.Append(}}
});context.AddSource(RegisterServiceHelper, SourceText.From(stringBuilder.ToString(), Encoding.UTF8));}}
如果您不熟悉Roslyn则这个方法可能看起来有点复杂。它使用Roslyn API来获取类的元数据-与Reflection类似。您可以检查文档以获取更多信息Work with syntax[33]Work with semantics[34]Explore code with the Roslyn syntax visualizer in Visual Studio[35]为了更好地检查项目中的语法树您可以从Visual Studio Installer安装**.NET Compiler Platform SDK**该工具为VS2019提供SyntaxVisualizer窗口。一旦找到由AutoRegister属性修饰的类就可以将注册实例的代码添加到源代码中。生成的代码将与原始代码一起编译。通过这种方式我们避免了Reflection的昂贵成本并提高了性能。最后我们可以在客户端中调用生成的代码private static void SourceGeneratorSample()
{Console.WriteLine(Here is the simple Source Generator sample:);CodeGeneratorDemo.SourceGeneratorDemo.SpeakerHelper.SayHello();Console.WriteLine();Console.WriteLine(Here is the AutoRegisterAttribute Source Generator sample:);CodeGeneratorDemo.SourceGeneratorDemo.RegisterHelper.RegisterServices();
}
您需要编译CodeGeneratorDemo.SourceGeneratorDemo项目重新打开VS2019。然后您可以看到如下输出Here is the AutoRegisterAttribute Source Generator sample:
CodeGeneratorDemo.Client.Core.OrderService constructed.
CodeGeneratorDemo.Client.Core.OrderService has been registered for CodeGeneratorDemo.Client.Core.IOrderService.
CodeGeneratorDemo.Client.Core.ProductService constructed.
CodeGeneratorDemo.Client.Core.ProductService has been registered for CodeGeneratorDemo.Client.Core.IProductService.
如果您在RegisterServices()方法上按F12检查它的定义可以发现生成的代码如下using System;
using CodeGeneratorDemo.SourceGeneratorDemo.Core;
namespace CodeGeneratorDemo.SourceGeneratorDemo
{public class RegisterHelper{public static void RegisterServices(){DiContainerMocker.RegisterServiceIProductService, ProductService(new ProductService());DiContainerMocker.RegisterServiceIOrderService, OrderService(new OrderService());}}
}
这正是我们想要的。很棒的事情是如果在某个Sevice上删除或添加了AutoRegister Attribute您将看到生成的代码将立即更新无需重新编译项目如何调试 Source Generators有时我们需要调试Source Generators。如果仅在Source Generator中设置一个断点您将发现它将无法工作。解决方案是在Initialize方法中附加调试器 public void Initialize(GeneratorInitializationContext context){
#if DEBUGif (!Debugger.IsAttached){Debugger.Launch();}
#endifcontext.RegisterForSyntaxNotifications(() new MySyntaxReceiver());}
然后您可以通过设置断点来调试Source Generator。如何处理复杂的模板代码在这两个示例中我演示了如何使用Source Generators生成代码。我们在Execute方法中使用了原始字符串——看起来很丑。更好的方法是使用模板引擎。一种可能的选择是Scriban[36]——一种用于.NET的快速强大安全和轻量级的脚本语言和引擎。因此我们可以将模板存储在单独的文件中这样项目会比较整洁。我不会深入探讨模板语法因为它不在本文讨论范围之内。您可以在其GitHub存储库中找到更多信息。使用场景Microsoft提供了一个Source Generators cookbook。您可以在GitHub上找到它https://github.com/dotnet/roslyn/blob/master/docs/features/source-generators.cookbook.md。您将看到Source Generators可以在许多情况下应用尤其是替换Reflection或开发样板代码时。例如某些JSON序列化经常使用动态分析例如使用Reflection在运行时检查类型。源代码生成器可以在编译时生成静态序列化代码以节省成本。您还可以访问其他文件例如XML或JSON文件来生成代码。在GitHub上查找更多示例https://github.com/dotnet/roslyn-sdk/tree/master/samples/CSharp/SourceGenerators。小结 在本文中我向您介绍了可用于在C#程序中生成代码的四种方式。它们可能适合不同的场景因此我们需要比较每种方法并选择适当的方式。场景优点缺点Code Snippets以特定格式创建代码块例如属性方法和类等。节省键入重复代码块的时间。仅适用于特定格式。无法自动更新。Reflection在运行时获取元数据然后与类属性方法等进行交互。在许多情况下功能强大且灵活。可以减少耦合。昂贵的成本。潜在的性能问题。维护更复杂。T4 Template用于生成一些样板代码。但是有时可以通过设计模式对其进行重构。可以从其他文件读取数据。许多可用的控制块。可以生成静态代码而不会出现性能问题。糟糕的编辑器支持。容易在模板中犯错误。Source Generators可用于替换一些Reflection代码。在基于Roslyn的编译中生成静态代码。没有性能问题。编译速度更快。支持智能感知。无法生成源代码时可以产生诊断信息。支持Partial Class或Partial Method。工具需要改进。有点难以上手。本文的重点是如何使用Source Generators-.NET 5中提供的新功能。它仍处于预览状态因此我们可能很快会看到Microsoft的更多改进。我的期望是与VS2019更好地集成。现在的体验还不够好因为我们必须反复重新打开VS。希望本文能帮助您节省C开发的时间。如果您有任何想法请随时发表您的评论。谢谢。参考资料[1]C# Code Snippets: https://docs.microsoft.com/en-us/visualstudio/ide/visual-csharp-code-snippets?viewvs-2019WT.mc_idDT-MVP-5001643[2]INotifyPropertyChanged: https://docs.microsoft.com/en-us/dotnet/api/system.componentmodel.inotifypropertychanged?WT.mc_idDT-MVP-5001643[3]Walkthrough: Create a code snippet: https://docs.microsoft.com/en-us/visualstudio/ide/walkthrough-creating-a-code-snippet?viewvs-2019WT.mc_idDT-MVP-5001643[4]Code snippet functions: https://docs.microsoft.com/en-us/visualstudio/ide/code-snippet-functions?viewvs-2019WT.mc_idDT-MVP-5001643[5]How to: Distribute code snippets: https://docs.microsoft.com/en-us/visualstudio/ide/how-to-distribute-code-snippets?viewvs-2019WT.mc_idDT-MVP-5001643[6]ASP.NET Core: https://docs.microsoft.com/en-us/aspnet/core/?viewaspnetcore-5.0WT.mc_idDT-MVP-5001643[7]Entity Framework Core: https://docs.microsoft.com/en-us/ef/core/?WT.mc_idDT-MVP-5001643[8]类型的: https://docs.microsoft.com/en-us/dotnet/api/system.type?WT.mc_idDT-MVP-5001643[9]System.Reflection: https://docs.microsoft.com/en-us/dotnet/api/system.reflection?WT.mc_idDT-MVP-5001643[10]System.Type: https://docs.microsoft.com/en-us/dotnet/api/system.type?WT.mc_idDT-MVP-5001643[11]Assembly: https://docs.microsoft.com/en-us/dotnet/api/system.reflection.assembly?WT.mc_idDT-MVP-5001643[12]Module: https://docs.microsoft.com/en-us/dotnet/api/system.reflection.module?WT.mc_idDT-MVP-5001643[13]ConstructorInfo: https://docs.microsoft.com/en-us/dotnet/api/system.reflection.constructorinfo?WT.mc_idDT-MVP-5001643[14]MethodInfo: https://docs.microsoft.com/en-us/dotnet/api/system.reflection.methodinfo?WT.mc_idDT-MVP-5001643[15]FieldInfo: https://docs.microsoft.com/en-us/dotnet/api/system.reflection.fieldinfo?WT.mc_idDT-MVP-5001643[16]EventInfo: https://docs.microsoft.com/en-us/dotnet/api/system.reflection.eventinfo?WT.mc_idDT-MVP-5001643[17]PropertyInfo: https://docs.microsoft.com/en-us/dotnet/api/system.reflection.propertyinfo?WT.mc_idDT-MVP-5001643[18]ParameterInfo: https://docs.microsoft.com/en-us/dotnet/api/system.reflection.parameterinfo?WT.mc_idDT-MVP-5001643[19]CustomAttributeData: https://docs.microsoft.com/en-us/dotnet/api/system.reflection.customattributedata?WT.mc_idDT-MVP-5001643[20]Reflection in .NET: https://docs.microsoft.com/en-us/dotnet/framework/reflection-and-codedom/reflection?WT.mc_idDT-MVP-5001643[21]Viewing Type Information: https://docs.microsoft.com/en-us/dotnet/framework/reflection-and-codedom/viewing-type-information?WT.mc_idDT-MVP-5001643[22]Dynamically Loading and Using Types: https://docs.microsoft.com/en-us/dotnet/framework/reflection-and-codedom/dynamically-loading-and-using-types?WT.mc_idDT-MVP-5001643[23]DevArt T4 Editor: https://www.devart.com/t4-editor/[24]Code Generation and T4 Text Templates: https://docs.microsoft.com/en-us/visualstudio/modeling/code-generation-and-t4-text-templates?viewvs-2019WT.mc_idDT-MVP-5001643[25]Walkthrough: Generate Code by using Text Templates: https://docs.microsoft.com/en-us/visualstudio/modeling/walkthrough-generating-code-by-using-text-templates?viewvs-2019WT.mc_idDT-MVP-5001643[26]Run-Time Text Generation with T4 Text Templates: https://docs.microsoft.com/en-us/visualstudio/modeling/run-time-text-generation-with-t4-text-templates?viewvs-2019WT.mc_idDT-MVP-5001643[27]T4 Text Template Directives: https://docs.microsoft.com/en-us/visualstudio/modeling/t4-text-template-directives?viewvs-2019WT.mc_idDT-MVP-5001643[28]Text Template Control Blocks: https://docs.microsoft.com/en-us/visualstudio/modeling/text-template-control-blocks?viewvs-2019WT.mc_idDT-MVP-5001643[29]Guidelines for Writing T4 Text Templates: https://docs.microsoft.com/en-us/visualstudio/modeling/guidelines-for-writing-t4-text-templates?viewvs-2019WT.mc_idDT-MVP-5001643[30].NET 5 SDK: https://dotnet.microsoft.com/download/dotnet/5.0[31]Attribute: https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/attributes/?WT.mc_idDT-MVP-5001643[32]Roslyn: https://docs.microsoft.com/en-us/dotnet/csharp/roslyn-sdk/?WT.mc_idDT-MVP-5001643[33]Work with syntax: https://docs.microsoft.com/en-us/dotnet/csharp/roslyn-sdk/work-with-syntax?WT.mc_idDT-MVP-5001643[34]Work with semantics: https://docs.microsoft.com/en-us/dotnet/csharp/roslyn-sdk/work-with-semantics?WT.mc_idDT-MVP-5001643[35]Explore code with the Roslyn syntax visualizer in Visual Studio: https://docs.microsoft.com/en-us/dotnet/csharp/roslyn-sdk/syntax-visualizer?tabscsharpWT.mc_idDT-MVP-5001643[36]Scriban: https://github.com/scriban/scriban