August 12 2024
dotnet new classlib -n MySourceGenerator
dotnet add package Microsoft.CodeAnalysis.CSharp
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
using System.Text;
[Generator]
public class HelloWorldGenerator : ISourceGenerator
{
public void Initialize(GeneratorInitializationContext context)
{
// Register any callbacks here
}
public void Execute(GeneratorExecutionContext context)
{
// Create the source code to inject
string sourceCode = @" using System; namespace HelloWorldGenerated { public static class HelloWorld { public static void SayHello() => Console.WriteLine(""Hello from the generated code!""); } }";
// Add the source code to the compilation
context.AddSource("HelloWorldGenerated", SourceText.From(sourceCode, Encoding.UTF8));
}
}
dotnet add reference ../MySourceGenerator/MySourceGenerator.csproj
// Program.cs
using HelloWorldGenerated;
class Program
{
static void Main(string[] args)
{
// Call the generated method
HelloWorld.SayHello();
}
}
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
using System.Text;
[Generator]
public class DIRegistrationGenerator : ISourceGenerator
{
public void Initialize(GeneratorInitializationContext context) { }
public void Execute(GeneratorExecutionContext context)
{
var sourceCode = new StringBuilder();
sourceCode.AppendLine("using Microsoft.Extensions.DependencyInjection;");
sourceCode.AppendLine("namespace DIRegistrationGenerated");
sourceCode.AppendLine("{");
sourceCode.AppendLine(" public static class DIExtensions");
sourceCode.AppendLine(" {");
sourceCode.AppendLine(" public static IServiceCollection AddGeneratedServices(this IServiceCollection services)");
sourceCode.AppendLine(" {");
var compilation = context.Compilation;
var interfaceType = compilation.GetTypeByMetadataName("MyNamespace.IMyService");
foreach (var typeSymbol in compilation.GetSymbolsWithName(name => name.EndsWith("Service"), SymbolFilter.Type))
{
if (typeSymbol is INamedTypeSymbol namedTypeSymbol && namedTypeSymbol.Interfaces.Contains(interfaceType))
{
sourceCode.AppendLine($" services.AddTransient<{namedTypeSymbol.ToDisplayString()}>();");
}
}
sourceCode.AppendLine(" return services;");
sourceCode.AppendLine(" }");
sourceCode.AppendLine(" }");
sourceCode.AppendLine("}");
context.AddSource("DIRegistrationExtensions", SourceText.From(sourceCode.ToString(), Encoding.UTF8));
}
}
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
using System.Text;
[Generator]
public class CompileTimeValidationGenerator : ISourceGenerator
{
public void Initialize(GeneratorInitializationContext context) { }
public void Execute(GeneratorExecutionContext context)
{
var compilation = context.Compilation;
var attributeSymbol = compilation.GetTypeByMetadataName("MyNamespace.MyCustomAttribute");
foreach (var classSymbol in compilation.GlobalNamespace.GetNamespaceMembers().SelectMany(ns => ns.GetTypeMembers()))
{
if (classSymbol.GetAttributes().Any(ad => ad.AttributeClass!.Equals(attributeSymbol, SymbolEqualityComparer.Default)))
{
var hasParameterlessConstructor = classSymbol.Constructors.Any(c => c.Parameters.IsEmpty);
if (!hasParameterlessConstructor)
{
var diagnostic = Diagnostic.Create(
new DiagnosticDescriptor(
"GEN002",
"Parameterless constructor required",
"Class {0} marked with [MyCustomAttribute] must have a parameterless constructor.",
"SourceGenerator",
DiagnosticSeverity.Error,
isEnabledByDefault: true),
Location.None,
classSymbol.Name);
context.ReportDiagnostic(diagnostic);
}
}
}
}
}
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
using System.Text;
[Generator]
public class CustomSerializationGenerator : ISourceGenerator
{
public void Initialize(GeneratorInitializationContext context) { }
public void Execute(GeneratorExecutionContext context)
{
var compilation = context.Compilation;
var serializableAttribute = compilation.GetTypeByMetadataName("System.SerializableAttribute");
foreach (var classSymbol in compilation.GlobalNamespace.GetNamespaceMembers().SelectMany(ns => ns.GetTypeMembers()))
{
if (classSymbol.GetAttributes().Any(ad => ad.AttributeClass!.Equals(serializableAttribute, SymbolEqualityComparer.Default)))
{
var sb = new StringBuilder();
sb.AppendLine($"namespace {classSymbol.ContainingNamespace}.Generated");
sb.AppendLine("{");
sb.AppendLine($" public static class {classSymbol.Name}Serializer");
sb.AppendLine(" {");
sb.AppendLine($" public static string Serialize({classSymbol.Name} obj)");
sb.AppendLine(" {");
sb.AppendLine(" // Custom serialization logic");
sb.AppendLine(" return string.Empty;");
sb.AppendLine(" }");
sb.AppendLine(" }");
sb.AppendLine("}");
context.AddSource($"{classSymbol.Name}Serializer", SourceText.From(sb.ToString(), Encoding.UTF8));
}
}
}
}
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
using System.Text;
[Generator]
public class ApiClientGenerator : ISourceGenerator
{
public void Initialize(GeneratorInitializationContext context) { }
public void Execute(GeneratorExecutionContext context)
{
// Pretend this string is loaded from a Swagger specification
var apiSpec = @" GET /api/values -> string[] POST /api/values -> void";
var sb = new StringBuilder();
sb.AppendLine("using System.Net.Http;");
sb.AppendLine("using System.Threading.Tasks;");
sb.AppendLine("namespace ApiClientGenerated");
sb.AppendLine("{");
sb.AppendLine(" public class ValuesApiClient");
sb.AppendLine(" {");
sb.AppendLine(" private readonly HttpClient _httpClient;");
sb.AppendLine(" public ValuesApiClient(HttpClient httpClient)");
sb.AppendLine(" {");
sb.AppendLine(" _httpClient = httpClient;");
sb.AppendLine(" }");
sb.AppendLine(" public async Task);
sb.AppendLine(" {");
sb.AppendLine(" var response = await _httpClient.GetAsync(\"/api/values\");");
sb.AppendLine(" response.EnsureSuccessStatusCode();");
sb.AppendLine(" return await response.Content.ReadAsAsync);
sb.AppendLine(" }");
sb.AppendLine(" public async Task PostValuesAsync(string[] values)");
sb.AppendLine(" {");
sb.AppendLine(" var response = await _httpClient.PostAsJsonAsync(\"/api/values\", values);");
sb.AppendLine(" response.EnsureSuccessStatusCode();");
sb.AppendLine(" }");
sb.AppendLine(" }");
sb.AppendLine("}");
context.AddSource("ValuesApiClient", SourceText.From(sb.ToString(), Encoding.UTF8));
}
}
// MyClass.cs
namespace MyNamespace
{
public class MyClass
{
public void MyMethod()
{
// Original method implementation
}
}
}
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
using System.Text;
[Generator]
public class FileBasedGenerator : ISourceGenerator
{
public void Initialize(GeneratorInitializationContext context) { }
public void Execute(GeneratorExecutionContext context)
{
// Read the content of MyClass.cs
var sourceFile = context.AdditionalFiles.FirstOrDefault(file => file.Path.EndsWith("MyClass.cs"));
if (sourceFile != null)
{
var fileContent = sourceFile.GetText(context.CancellationToken)?.ToString();
// Generate additional code based on the content of MyClass.cs
if (fileContent != null)
{
var generatedCode = $@" namespace MyNamespace.Generated {{ public static class MyClassExtensions {{ public static void PrintInfo(this MyClass instance) {{ Console.WriteLine(""MyMethod was called in MyClass""); }} }} }}";
// Add the generated code to the compilation
context.AddSource("MyClassExtensions", SourceText.From(generatedCode, Encoding.UTF8));
}
}
}
}
}
<!-- ConsumingProject.csproj -->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Compile Include="MyClass.cs" />
<AdditionalFiles Include="MyClass.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MySourceGenerator\MySourceGenerator.csproj" />
</ItemGroup>
</Project>
// Program.cs
using MyNamespace;
using MyNamespace.Generated;
class Program
{
static void Main(string[] args)
{
var myClass = new MyClass();
myClass.MyMethod();
// Use the generated extension method
myClass.PrintInfo();
}
}
Join 14,250+ subscribers to improve your .NET Knowledge.
Go-to resource for understanding the core concepts of design patterns without the overwhelming complexity. In this concise and affordable ebook, I've distilled the essence of design patterns into an easy-to-digest format. It is a Beginner level. Check out it here.
Every Monday morning, I share 1 actionable tip on C#, .NET & Arcitecture topic, that you can use right away.
Subscribe to the TheCodeMan.net and be among the 14,250+ subscribers gaining practical tips and resources to enhance your .NET expertise.