还在为写重复代码而烦恼?还在因为手动维护样板代码而加班?如果你是一位C#开发者,那么今天要分享的这个编译时黑科技绝对会让你眼前一亮!
C# 9.0引入的Source Generators不仅能够自动生成代码,还能在编译时就完成所有工作,零运行时开销!想象一下,只需要加个特性标记,编译器就能自动为你生成所需的代码,这种开发体验简直不要太爽!
本文将从实战角度带你掌握Source Generators的核心技术,让你的C#开发更加高效和优雅。
Source Generators是基于Roslyn编译器平台的编译时代码生成器,它具有两个核心能力:
相比T4模板或反射等传统方式,Source Generators有着显著优势:
让我们通过一个实际例子来掌握Source Generators的开发流程。
C#MyGenerator(解决方案)
├── AppMyGenerator.csproj (类库, netstandard2.0)
│ ├── GenerateDateTimeAttribute.cs
│ └── GenerateDateTimeAttribute.cs
└── AppMyGeneratorTest.csproj
XML<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>9.0</LangVersion>
<IncludeBuildOutput>false</IncludeBuildOutput>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<DevelopmentDependency>true</DevelopmentDependency>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.0.1" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" PrivateAssets="all" />
</ItemGroup>
</Project>
首先创建一个用于标记目标类的特性:
C#using System;
namespace AppMyGenerator
{
[AttributeUsage(AttributeTargets.Class)]
public class GenerateDateTimeAttribute : Attribute
{
}
}
创建实现ISourceGenerator
接口的生成器类:
C#using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
using Microsoft.CodeAnalysis;
using System.Linq;
namespace AppMyGenerator
{
[Generator]
public class MyGeneratorClass : ISourceGenerator
{
public void Initialize(GeneratorInitializationContext context)
{
context.RegisterForSyntaxNotifications(() => new SyntaxReceiver());
}
public void Execute(GeneratorExecutionContext context)
{
var compilation = context.Compilation;
var receiver = (SyntaxReceiver)context.SyntaxReceiver;
if (receiver == null) return;
foreach (var classDeclaration in receiver.CandidateClasses)
{
var model = compilation.GetSemanticModel(classDeclaration.SyntaxTree);
var classSymbol = model.GetDeclaredSymbol(classDeclaration);
if (classSymbol == null) continue;
var namespaceName = classSymbol.ContainingNamespace.ToDisplayString();
var originalClassName = classSymbol.Name;
// 生成源代码 - 扩展原类
var source = GenerateClassSource(originalClassName, namespaceName);
context.AddSource($"{originalClassName}_Generated.cs", SourceText.From(source, Encoding.UTF8));
}
}
private string GenerateClassSource(string className, string namespaceName)
{
var sourceBuilder = new StringBuilder();
sourceBuilder.AppendLine("using System;");
sourceBuilder.AppendLine();
sourceBuilder.AppendLine($"namespace {namespaceName}");
sourceBuilder.AppendLine("{");
sourceBuilder.AppendLine($"\tpublic partial class {className}");
sourceBuilder.AppendLine("\t{");
sourceBuilder.AppendLine("\t\tpublic static string CurrentDateTime => DateTime.Now.ToString();");
sourceBuilder.AppendLine("\t}");
sourceBuilder.AppendLine("}");
return sourceBuilder.ToString();
}
}
internal class SyntaxReceiver : ISyntaxReceiver
{
public List<ClassDeclarationSyntax> CandidateClasses { get; } = new();
public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
{
if (syntaxNode is not ClassDeclarationSyntax classDeclaration ||
classDeclaration.AttributeLists.Count <= 0) return;
foreach (var attributeList in classDeclaration.AttributeLists)
{
var hasAttribute = attributeList.Attributes
.Select(attr => attr.Name.ToString())
.Any(name => name is "GenerateDateTime" or "GenerateDateTimeAttribute");
if (hasAttribute)
{
CandidateClasses.Add(classDeclaration);
break;
}
}
}
}
}
创建测试项目来验证生成器:
XML<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<LangVersion>9.0</LangVersion>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\AppMyGenerator\AppMyGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="true" />
</ItemGroup>
</Project>
C#using System;
using AppMyGenerator;
namespace AppMyGeneratorTest
{
[GenerateDateTime]
public partial class Program
{
static void Main(string[] args)
{
// 现在可以直接使用生成的方法
Console.WriteLine($"Hello, world! 当前时间: {Program.CurrentDateTime}");
Console.WriteLine("Done");
Console.ReadLine();
}
}
}
运行结果:
#if DEBUG
条件编译来添加调试信息context.ReportDiagnostic
输出诊断信息Source Generators的威力远不止于此,以下是一些高级应用场景:
C#[AutoMap(typeof(UserEntity))]
public class UserDto
{
// 自动生成映射方法
}
C#[GeneratePerformanceCounters]
public class ApiController
{
// 自动生成性能监控代码
}
C#[GenerateApiClient("https://api.example.com/swagger.json")]
public partial class ApiClient
{
// 根据Swagger文档自动生成客户端方法
}
Source Generators作为C#编译时代码生成的革命性工具,为我们带来了三个核心价值:
通过今天的实战练习,相信你已经掌握了Source Generators的基础用法。从简单的属性生成到复杂的业务逻辑自动化,这个技术将成为你C#开发路上的得力助手。
你在实际项目中遇到过哪些重复代码的痛点?想用Source Generators解决什么样的问题?欢迎在评论区分享你的想法和经验!
觉得这篇文章对你有帮助?别忘了转发给更多的C#同行! 让我们一起拥抱更高效的编程方式!
#C#开发 #编程技巧 #代码生成 #性能优化
本文作者:技术老小子
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!