C# Source Generators: Let Your Code Write Code for You

C# Source Generators: Let Your Code Write Code for You

In the world of software development, efficiency and productivity are key. C# has introduced a powerful feature called Source Generators that can significantly enhance developer productivity by automating repetitive tasks.

In this blog, we’ll explore what Source Generators are, their history, why they are important, and how they can be used with a practical example.

What is a Source Generator in C#?

A Source Generator is a feature in C# that allows developers to automatically generate additional C# code during compilation. It is part of the Roslyn compiler platform and enables you to write code that inspects your project and generates new code based on that analysis.

Key Features of Source Generators:

  • Compile-Time Code Generation: Code is generated during compilation, not at runtime.

  • No Runtime Overhead: Since the code is generated at compile time, there is no performance penalty at runtime.

Source Generators are particularly useful for tasks like:

  • Automatically implementing boilerplate code (e.g., ToString, Equals, GetHashCode).

  • Generating serialization/deserialization logic.

  • Creating strongly-typed wrappers for APIs or configurations.

  • Custom model or DTO class etc.

History of Source Generators in .NET

Source Generators were introduced in .NET 5 as part of the Roslyn compiler. Here’s a brief timeline:

  • .NET 5 (2020): Source Generators were officially released, allowing developers to generate C# code during compilation.

  • .NET 6 (2021): Improvements were made to Source Generators, including better performance and tooling support.

  • .NET 7 (2022): Further enhancements were introduced, such as incremental generators for better performance and reduced memory usage.

Source Generators have become a cornerstone of modern C# development, enabling developers to write less repetitive code and focus on solving business problems.

Why Are Source Generators?

Source Generators are a game-changer for developers. Here’s why they are important:

  • Boosts Productivity

    • Automates Repetitive Tasks: Developers no longer need to manually write boilerplate code (e.g., ToString, Equals, etc.).

    • Reduces Errors: Automatically generated code is consistent and less prone to human error.

  • Improves Code Maintainability

    • Keeps Code DRY (Don’t Repeat Yourself): Generated code ensures consistency across the codebase.

    • Encapsulates Logic: Source Generators encapsulate complex logic, making the main codebase cleaner and easier to understand.

  • Enhances Performance

    • Compile-Time Generation: Since code is generated at compile time, there is no runtime overhead.

    • Incremental Generators: Introduced in .NET 6, these ensure that only the necessary code is regenerated, improving performance.

Example: Generating a ToString Method

Overview:

The ToStringGenerator is a C# Source Generator that automatically generates a ToString method for classes marked with a custom attribute (GenerateToString). The generated ToString method concatenates the values of all public properties in the class.

Key Components

  • IIncrementalGenerator

    • The generator implements IIncrementalGenerator, which is part of the Roslyn API for creating source generators.

    • It allows incremental generation, meaning it only regenerates code when necessary, improving performance.

  • Initialize Method

    • This is the entry point for the generator.

    • It sets up the pipeline for identifying classes with the GenerateToString attribute and generating the ToString method.

  • Step-by-Step Breakdown

    • Create a C# class library project and add required dependencies

  • Create a ToStringGenerator named public class

  • Identify Target Classes

    • The generator uses a syntax provider to find classes in the codebase.

    • The IsSyntaxTarget method filters classes that have at least one attribute (AttributeLists.Count > 0)

    •             private static bool IsSyntaxTarget(SyntaxNode node)
                  {
                      return node is ClassDeclarationSyntax classDeclarationSyntax
                          && classDeclarationSyntax.AttributeLists.Count > 0;
                  }
      
  • Filter Classes with GenerateToString Attribute

    • The GetSemanticTarget method checks if a class has the GenerateToString attribute.

    • It iterates through the attributes of the class and looks for GenerateToString or GenerateToStringAttribute.

    •             private static ClassDeclarationSyntax? GetSemanticTarget(
                      GeneratorSyntaxContext context)
                  {
                      var classDeclarationSyntax = (ClassDeclarationSyntax)context.Node;
      
                      foreach (var attributeListSyntax in classDeclarationSyntax.AttributeLists)
                      {
                          foreach (var attributeSyntax in attributeListSyntax.Attributes)
                          {
                              var attributeName = attributeSyntax.Name.ToString();
      
                              if (attributeName == "GenerateToString"
                                  || attributeName == "GenerateToStringAttribute")
                              {
                                  return classDeclarationSyntax;
                              }
                          }
                      }
      
                      return null;
                  }
      
  • Register the Source Generator: The Initialize method registers the generator pipeline:

    • It uses CreateSyntaxProvider to find classes with the GenerateToString attribute.

    • It filters out null targets and registers the Execute method to generate the ToString method.

    •             public void Initialize(IncrementalGeneratorInitializationContext context)
                  {
                      var classes = context.SyntaxProvider.CreateSyntaxProvider(
                          predicate: static (node, _) => IsSyntaxTarget(node),
                          transform: static (ctx, _) => GetSemanticTarget(ctx))
                          .Where(static (target) => target is not null);
      
                      context.RegisterSourceOutput(classes,
                          static (ctx, source) => Execute(ctx, source!));
      
                      context.RegisterPostInitializationOutput(
                          static (ctx) => PostInitializationOutput(ctx));
                  }
      
  • Generate the GenerateToString Attribute

    • The PostInitializationOutput method generates the

    • This ensures the attribute is available for use in the code.

    •             private static void PostInitializationOutput(
                      IncrementalGeneratorPostInitializationContext context)
                  {
                      context.AddSource("SourceGen.Generators.GenerateToStringAttribute.g.cs",
                          @"namespace SourceGen.Generators
                          {
                              internal class GenerateToStringAttribute : System.Attribute { }
                          }");
                  }
      
  • Generate the ToString Method

    • The Execute method generates the ToString method for each target class.

    • It uses a StringBuilder to construct the source code for the ToString method.

    • The method concatenates the values of all public properties in the class.

    •             private static void Execute(SourceProductionContext context,
                      ClassDeclarationSyntax classDeclarationSyntax)
                  {
                      if (classDeclarationSyntax.Parent
                          is BaseNamespaceDeclarationSyntax namespaceDeclarationSyntax)
                      {
                          var namespaceName = namespaceDeclarationSyntax.Name.ToString();
                          var className = classDeclarationSyntax.Identifier.Text;
                          var fileName = $"{namespaceName}.{className}.g.cs";
      
                          var stringBuilder = new StringBuilder();
                          stringBuilder.Append($@"
                  namespace {namespaceName}
                  {{
                      partial class {className}
                      {{
                          public override string ToString()
                          {{
                              return $""");
      
                              var first = true;
                              foreach (var memberDeclarationSyntax in classDeclarationSyntax.Members)
                              {
                                  if (memberDeclarationSyntax
                                      is PropertyDeclarationSyntax propertyDeclarationSyntax
                                      && propertyDeclarationSyntax.Modifiers.Any(SyntaxKind.PublicKeyword))
                                  {
                                      if (first)
                                      {
                                          first = false;
                                      }
                                      else
                                      {
                                          stringBuilder.Append("; ");
                                      }
                                      var propertyName = propertyDeclarationSyntax.Identifier.Text;
                                      stringBuilder.Append($"{propertyName}:{{{propertyName}}}");
                                  }
                              }
      
                              stringBuilder.Append($@""";
                          }}
                      }}
                  }}");
      
                          context.AddSource(fileName, stringBuilder.ToString());
                      }
                  }
      
  • Create a new console app and add project reference to SourceGen.Generators.

  • Add a partial class Customer with First Name and Last Name properties. As well as GenerateToString attribute on it.

    •         using SourceGen.Generators;
      
              namespace SourceGen.ConsoleApp;
      
              [GenerateToString]
              public partial class Customer
              {
                  public string? FirstName { get; set; }
                  public string? LastName { get; set; }
              }
      
  • Create a customer object and call ToString method—

using SourceGen.ConsoleApp;


var customer = new Customer
{
    FirstName = "Khairul",
    LastName = "Taher"
};

var customerAsString = customer.ToString();

Console.WriteLine(customerAsString);
Console.ReadLine();

  • Now add new property middle name in Customer class and update customer object then run the project again—

      using SourceGen.Generators;
    
      namespace SourceGen.ConsoleApp;
    
      [GenerateToString]
      public partial class Customer
      {
          public string? FirstName { get; set; }
          public string? MiddleName { get; set; }
          public string? LastName { get; set; }
      }
    
using SourceGen.ConsoleApp;


var customer = new Customer
{
    FirstName = "Khairul",
    MiddleName = "Alam",
    LastName = "Taher"
};

var customerAsString = customer.ToString();

Console.WriteLine(customerAsString);
Console.ReadLine();
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System.Text;

namespace SourceGen.Generators;

[Generator]
public class ToStringGenerator : IIncrementalGenerator
{
    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        var classes = context.SyntaxProvider.CreateSyntaxProvider(
            predicate: static (node, _) => IsSyntaxTarget(node),
            transform: static (ctx, _) => GetSemanticTarget(ctx))
            .Where(static (target) => target is not null);

        context.RegisterSourceOutput(classes,
            static (ctx, source) => Execute(ctx, source!));

        context.RegisterPostInitializationOutput(
            static (ctx) => PostInitializationOutput(ctx));
    }

    private static bool IsSyntaxTarget(SyntaxNode node)
    {
        return node is ClassDeclarationSyntax classDeclarationSyntax
            && classDeclarationSyntax.AttributeLists.Count > 0;
    }

    private static ClassDeclarationSyntax? GetSemanticTarget(
        GeneratorSyntaxContext context)
    {
        var classDeclarationSyntax = (ClassDeclarationSyntax)context.Node;

        foreach (var attributeListSyntax in classDeclarationSyntax.AttributeLists)
        {
            foreach (var attributeSyntax in attributeListSyntax.Attributes)
            {
                var attributeName = attributeSyntax.Name.ToString();

                if (attributeName == "GenerateToString"
                    || attributeName == "GenerateToStringAttribute")
                {
                    return classDeclarationSyntax;
                }
            }
        }

        return null;
    }

    private static void PostInitializationOutput(
        IncrementalGeneratorPostInitializationContext context)
    {
        context.AddSource("SourceGen.Generators.GenerateToStringAttribute.g.cs",
            @"namespace SourceGen.Generators
            {
                internal class GenerateToStringAttribute : System.Attribute { }
            }");
    }

    private static void Execute(SourceProductionContext context,
        ClassDeclarationSyntax classDeclarationSyntax)
    {
        if (classDeclarationSyntax.Parent
            is BaseNamespaceDeclarationSyntax namespaceDeclarationSyntax)
        {
            var namespaceName = namespaceDeclarationSyntax.Name.ToString();
            var className = classDeclarationSyntax.Identifier.Text;
            var fileName = $"{namespaceName}.{className}.g.cs";

            var stringBuilder = new StringBuilder();
            stringBuilder.Append($@"
            namespace {namespaceName}
            {{
                partial class {className}
                {{
                    public override string ToString()
                    {{
                        return $""");

                        var first = true;
                        foreach (var memberDeclarationSyntax in classDeclarationSyntax.Members)
                        {
                            if (memberDeclarationSyntax
                                is PropertyDeclarationSyntax propertyDeclarationSyntax
                                && propertyDeclarationSyntax.Modifiers.Any(SyntaxKind.PublicKeyword))
                            {
                                if (first)
                                {
                                    first = false;
                                }
                                else
                                {
                                    stringBuilder.Append("; ");
                                }
                                var propertyName = propertyDeclarationSyntax.Identifier.Text;
                                stringBuilder.Append($"{propertyName}:{{{propertyName}}}");
                            }
                        }

                        stringBuilder.Append($@""";
                    }}
                }}
            }}");

            context.AddSource(fileName, stringBuilder.ToString());
        }
    }
}

Example: Create Model and DTOs referencing database tables

Suppose You have your database CodeGen with two tables. For a new project You want to create all Model and DTOs by referencing database tables—
img

  1. First of all, read the schema information from the database then map those to TableInfo and ColumnInfo class—
    •               using System.Text;
      
                    namespace CodeGen;
      
                    using Microsoft.Data.SqlClient;
                    using System.Collections.Generic;
      
                    public class DatabaseSchemaScanner
                    {
                        private string _connectionString;
      
                        public DatabaseSchemaScanner(string connectionString) {
                            _connectionString = connectionString;
                        }
                        public List<TableInfo> GetTables() {
                            List<TableInfo> tables = new List<TableInfo>();
                            using (SqlConnection connection = new SqlConnection(_connectionString)) {
                                connection.Open();
                                SqlCommand command = 
                                    new SqlCommand("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE'", connection);
                                SqlDataReader reader = command.ExecuteReader();
                                while (reader.Read()) {
                                    string tableName = reader.GetString(0);
                                    tables.Add(new TableInfo { Name = tableName, Columns = GetColumns(tableName, connection) });
                                }
                            }
      
                            return tables;
                        }
                        private List<ColumnInfo> GetColumns(string tableName, SqlConnection connection) {
                            List<ColumnInfo> columns = new List<ColumnInfo>();
                            SqlCommand command = new SqlCommand($@"
                                            SELECT 
                                                    TABLE_SCHEMA 
                                                    ,TABLE_NAME 
                                                    ,COLUMN_NAME 
                                                    ,CAST(ORDINAL_POSITION as nvarchar(10)) ORDINAL_POSITION
                                                    ,ISNULL(COLUMN_DEFAULT, '') COLUMN_DEFAULT
                                                    ,ic.IS_NULLABLE 
                                                    ,DATA_TYPE  
                                                    ,ISNULL(CAST(CHARACTER_MAXIMUM_LENGTH as nvarchar(10)), '') CHARACTER_MAXIMUM_LENGTH
                                                    ,ISNULL(CAST(NUMERIC_PRECISION as nvarchar(10)), '') NUMERIC_PRECISION
                                                    ,CAST(is_identity as nvarchar(10)) IsIdentity
                                            FROM INFORMATION_SCHEMA.COLUMNS  ic 
                                            INNER JOIN sys.objects so ON ic.TABLE_NAME = so.name
                                            INNER JOIN sys.columns sc on so.object_id = sc.object_id AND ic.COLUMN_NAME = sc.name
                                            WHERE TABLE_NAME = '{tableName}'
                                            ORDER BY ORDINAL_POSITION ASC", connection);
                            SqlDataReader reader = command.ExecuteReader();
                            while (reader.Read()) {
                                columns.Add(new ColumnInfo {
                                    SchemaName = reader.GetString(0),
                                    TableName = reader.GetString(1),
                                    ColumnName = reader.GetString(2),
                                    OrdinalPosition = reader.GetString(3),
                                    ColumnDefault = reader.GetString(4),
                                    IsNullable = reader.GetString(5),
                                    DataType = reader.GetString(6),
                                    CharecterMaxLength = reader.GetString(7),
                                    NumericPrecision = reader.GetString(8),
                                    IsIdentity = reader.GetString(9),
                                });
                            }
      
                            return columns;
                        }
                    }
      
                    public class TableInfo
                    {
                        public string Name { get; set; }
                        public List<ColumnInfo> Columns { get; set; }
                    }
                    public class ColumnInfo
                    {
                        public string SchemaName { get; set; }
                        public string TableName { get; set; }
                        public string ColumnName { get; set; }
                        public string OrdinalPosition { get; set; }
                        public string ColumnDefault { get; set; }
                        public string IsNullable { get; set; }
                        public string DataType { get; set; }
                        public string CharecterMaxLength { get; set; }
                        public string NumericPrecision { get; set; }
                        public string IsIdentity { get; set; }
                    }
      
      
      
  • Now create GenerateEntityClass function that take TableInfo as param and using SyntaxFactory create the model class step by step —

    •            static void GenerateEntityClass(TableInfo table, string basePath, string projectName) {
                     string domainPath = Path.Combine(basePath, "src", $"{projectName}.Domain", "Entities");
                     Directory.CreateDirectory(domainPath);
      
                     // Create class declaration
                     var classDeclaration = SyntaxFactory.ClassDeclaration(table.Name)
                         .AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword));
      
                     // Add properties with attributes
                     foreach (var column in table.Columns) {
                         // Create property declaration
                         var dataType = column.DataType.ToLower() switch {
                             "int" => "int",
                             "nvarchar" => "string",// Remove single quotes for strings
                             "decimal" => "decimal",
                             "bool" => "bool",
                             _ => "string"
                         };
      
                         // Property declaration
                         var propertyDeclaration = SyntaxFactory.PropertyDeclaration(
                                 SyntaxFactory.ParseTypeName(dataType),
                                 SyntaxFactory.Identifier(column.ColumnName))
                             .AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword))
                             .AddAccessorListAccessors(
                                 SyntaxFactory.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration).WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)),
                                 SyntaxFactory.AccessorDeclaration(SyntaxKind.SetAccessorDeclaration).WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)));
      
                         // Add [Key] attribute for the first column (assuming it's the primary key)
                         if (column.OrdinalPosition == "1") {
                             propertyDeclaration = propertyDeclaration.AddAttributeLists(
                                 SyntaxFactory.AttributeList(SyntaxFactory.SingletonSeparatedList(
                                     SyntaxFactory.Attribute(SyntaxFactory.IdentifierName("Key")))));
                         }
      
                         // Add [Required] attribute for non-nullable columns
                         if (column.IsNullable == "NO") {
                             propertyDeclaration = propertyDeclaration.AddAttributeLists(
                                 SyntaxFactory.AttributeList(SyntaxFactory.SingletonSeparatedList(
                                     SyntaxFactory.Attribute(SyntaxFactory.IdentifierName("Required")))));
                         }
      
                         // Add [MaxLength] attribute for columns with character maximum length
                         if (!string.IsNullOrEmpty(column.CharecterMaxLength)) {
                             propertyDeclaration = propertyDeclaration.AddAttributeLists(
                                 SyntaxFactory.AttributeList(SyntaxFactory.SingletonSeparatedList(
                                     SyntaxFactory.Attribute(SyntaxFactory.IdentifierName("MaxLength"))
                                         .AddArgumentListArguments(
                                             SyntaxFactory.AttributeArgument(
                                                 SyntaxFactory.LiteralExpression(
                                                     SyntaxKind.NumericLiteralExpression,
                                                     SyntaxFactory.Literal(int.Parse(column.CharecterMaxLength))))))));
                         }
      
                         // Add [DefaultValue] attribute if ColumnDefault is provided
                         if (!string.IsNullOrEmpty(column.ColumnDefault)) {
                             // Parse the default value based on the data type
                             var defaultValue = column.DataType.ToLower() switch {
                                 "int" => SyntaxFactory.LiteralExpression(
                                     SyntaxKind.NumericLiteralExpression,
                                     SyntaxFactory.Literal(int.Parse(column.ColumnDefault))),
                                 "nvarchar" => SyntaxFactory.LiteralExpression(
                                     SyntaxKind.StringLiteralExpression,
                                     SyntaxFactory.Literal(column.ColumnDefault.Trim('\''))), // Remove single quotes for strings
                                 "decimal" => SyntaxFactory.LiteralExpression(
                                     SyntaxKind.NumericLiteralExpression,
                                     SyntaxFactory.Literal(decimal.Parse(column.ColumnDefault))),
                                 "bool" => SyntaxFactory.LiteralExpression(
                                     SyntaxKind.TrueLiteralExpression), // Assuming "1" or "true" for boolean
                                 _ => SyntaxFactory.LiteralExpression(
                                     SyntaxKind.DefaultLiteralExpression)
                             };
      
                             propertyDeclaration = propertyDeclaration.AddAttributeLists(
                                 SyntaxFactory.AttributeList(SyntaxFactory.SingletonSeparatedList(
                                     SyntaxFactory.Attribute(SyntaxFactory.IdentifierName("DefaultValue"))
                                         .AddArgumentListArguments(
                                             SyntaxFactory.AttributeArgument(defaultValue)))));
                         }
      
                         // Add [DatabaseGenerated(DatabaseGeneratedOption.Identity)] if IsIdentity is "1"
                         if (column.IsIdentity == "1") {
                             propertyDeclaration = propertyDeclaration.AddAttributeLists(
                                 SyntaxFactory.AttributeList(SyntaxFactory.SingletonSeparatedList(
                                     SyntaxFactory.Attribute(SyntaxFactory.IdentifierName("DatabaseGenerated"))
                                         .AddArgumentListArguments(
                                             SyntaxFactory.AttributeArgument(
                                                 SyntaxFactory.MemberAccessExpression(
                                                     SyntaxKind.SimpleMemberAccessExpression,
                                                     SyntaxFactory.IdentifierName("DatabaseGeneratedOption"),
                                                     SyntaxFactory.IdentifierName("Identity")))))));
                         }
      
                         // Add the property to the class
                         classDeclaration = classDeclaration.AddMembers(propertyDeclaration);
                     }
      
                     // Create namespace declaration
                     var namespaceDeclaration = SyntaxFactory.NamespaceDeclaration(SyntaxFactory.ParseName($"{projectName}.Domain.Entities"))
                         .AddMembers(classDeclaration);
      
                     // Create compilation unit
                     var compilationUnit = SyntaxFactory.CompilationUnit()
                         .AddUsings(
                             SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("System.ComponentModel.DataAnnotations")),
                             SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("System.ComponentModel.DataAnnotations.Schema")),
                             SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("System.ComponentModel"))) // For DefaultValue
                         .AddMembers(namespaceDeclaration);
      
                     // Write the file
                     File.WriteAllText(Path.Combine(domainPath, $"{table.Name}.cs"), compilationUnit.NormalizeWhitespace().ToFullString());
                 }
      
  • Call the GenerateEntityClass from GenerateCodeFromSchema function for each table—

    •             static void GenerateCodeFromSchema(List<TableInfo> tables, string basePath, string projectName) {
                      foreach (var table in tables) {
                          GenerateEntityClass(table, basePath, projectName);
                          GenerateDtoClass(table, basePath, projectName);
                          GenerateCommandAndQueryClasses(table, basePath, projectName);
                          GenerateValidatorClass(table, basePath, projectName);
                          GenerateControllerClass(table, basePath, projectName);
      
                          // Generate Tests
                          GenerateUnitTests(table, basePath, projectName);
                          GenerateIntegrationTests(table, basePath, projectName);
                      }
      
                      GenerateDbContextClass(tables, basePath, projectName);
                      GenerateProgramFile(basePath, projectName);
                  }
      
  • It is time to call the GenerateCodeFromSchema from main of program class and see the magic—

    •             static void Main(string[] args) {
                      //Console.WriteLine("Enter project name:");
                      string projectName = "TstCodegen";//Console.ReadLine();
                      string connectionString = "Server=CTS-VIVASOFT;Database=CodeGen;Persist Security Info=True;User ID=sa;Password=sa123;MultipleActiveResultSets=true;TrustServerCertificate=true;Connection Timeout=120";//Console.ReadLine();
                      DatabaseSchemaScanner databaseSchemaScanner = new DatabaseSchemaScanner(connectionString);
                      List<TableInfo> tables = databaseSchemaScanner.GetTables();
      
                      // Create project directory
                      string basePath = Path.Combine(Directory.GetCurrentDirectory(), projectName);
                      Directory.CreateDirectory(basePath);
      
                      // Generate code based on the database schema
                      GenerateCodeFromSchema(tables, basePath, projectName);
      
                      // Generate .csproj files
                      GenerateCsprojFiles(basePath, projectName);
      
                      // Generate solution file
                      GenerateSolutionFile(basePath, projectName);
      
                      Console.WriteLine($"Project '{projectName}' generated successfully at {basePath}");
                  }
      
  • Got to \bin\Debug\net8.0\[name your project]

  • Run this new generated project and see all of the model and available into the mapped directory—

Source Generators in C# are a powerful tool for automating repetitive tasks, improving code maintainability, and boosting developer productivity. By generating code at compile time, they eliminate the need for manual boilerplate code and ensure consistency across your code base.

In this blog, we explored what Source Generators are, their history, why they are important, and how to use them with a practical example. Whether you’re generating ToString methods, serialization logic, or API wrappers, Source Generators can save you time and effort, allowing you to focus on solving real-world problems.