Skip to content

BoBoBaSs84/BB84.SourceGenerators

Repository files navigation

BB84.SourceGenerators

A collection of C# source generators that automatically generate boilerplate code at compile time, reducing manual coding and improving code maintainability.

CI CD CodeQL Dependabot

License: MIT C# .NET Standard 2.0 Issues LastCommit PullRequests RepoSize NuGet

Features

This package provides fifteen powerful source generators:

  • Abstraction Generator - Interface and implementation generation for static classes
  • AutoMapper Generator - Compile-time property-to-property mapping method generation
  • Assembly Information Generator - Compile-time assembly metadata constants without reflection
  • Builder Generator - Fluent builder pattern generation for classes
  • Cloneable Generator - Compile-time Clone() and DeepClone() method generation with collection and struct support
  • Decorator Generator - Compile-time decorator pattern generation with full interface delegation, generic methods, events, and partial method hooks
  • Disposable Generator - Compile-time IDisposable / IAsyncDisposable pattern generation with ordered resource cleanup
  • Enumerator Extensions Generator - Fast, allocation-free extension methods for enums
  • Equality Generator - Compile-time Equals, GetHashCode, and operator generation
  • Factory Generator - Compile-time factory pattern generation with automatic implementation discovery
  • INI File Generator - Compile-time INI file serialization and deserialization
  • Notification Properties Generator - Configurable INotifyPropertyChanged/INotifyPropertyChanging implementation
  • Singleton Generator - Thread-safe singleton pattern generation with lazy or eager initialization
  • ToString Generator - Compile-time ToString() override generation
  • Validator Generator - Compile-time data annotation validation

Installation

Install the package via NuGet:

dotnet add package BB84.SourceGenerators

Or via Package Manager Console:

Install-Package BB84.SourceGenerators

In the project where you want to use the generators, add a reference to the package and include the appropriate using directive for the attributes:

using BB84.SourceGenerators.Attributes;

If the attributes are not recognized, make shure that the EmitCompilerGeneratedFiles property is set to true in the project file:

<PropertyGroup>
  <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
</PropertyGroup>

Usage

1. Enumerator Extensions Generator

Generates high-performance extension methods for enumerations, providing faster alternatives to Enum.ToString(), Enum.IsDefined(), Enum.GetNames(), and Enum.GetValues().

Attribute

[GenerateEnumeratorExtensions]

Example

using BB84.SourceGenerators.Attributes;

[GenerateEnumeratorExtensions]
public enum Status
{
    [System.ComponentModel.Description("Pending approval")]
    Pending = 0,
    Active = 1,
    [System.ComponentModel.Description("Temporarily inactive")]
    Inactive = 2,
    Deleted = 3
}

Generated Methods

The generator creates the following extension methods:

  • ToStringFast() - Returns the name of the enum value as a string
  • IsDefinedFast(this TEnum value) - Checks if an enum value is defined
  • IsDefinedFast(this string name) - Checks if an enum name is defined
  • GetNamesFast() - Returns all enum names as an IEnumerable<string>
  • GetValuesFast() - Returns all enum values as an IEnumerable<TEnum>
  • GetDescriptionFast() - Returns the description from [Description] attribute, or the name if not present

Usage Example

var status = Status.Pending;

// Fast string conversion
string name = status.ToStringFast(); // "Pending"

// Check if defined
bool isDefined = status.IsDefinedFast(); // true
bool isNameDefined = "Active".IsDefinedFast(); // true

// Get description
string description = status.GetDescriptionFast(); // "Pending approval"

// Get all names and values
IEnumerable<string> names = status.GetNamesFast();
IEnumerable<Status> values = status.GetValuesFast();

2. Notification Properties Generator

Automatically generates properties with INotifyPropertyChanged and/or INotifyPropertyChanging support for private fields in a class. Both interfaces are independently configurable.

Attributes

[GenerateNotifications(bool propertyChanged = true, bool propertyChanging = true, bool hasChanged = false)]
[AlsoNotify(params string[] propertyNames)]
[ExcludeFromNotification]

Parameters:

  • propertyChanged - When true (default), implements INotifyPropertyChanged and generates PropertyChanged event and RaisePropertyChanged() method
  • propertyChanging - When true (default), implements INotifyPropertyChanging and generates PropertyChanging event and RaisePropertyChanging() method
  • hasChanged - When true, generates an additional HasChanged boolean property that is set to true when any property changes

Field-Level Attributes:

  • [AlsoNotify(...)] - Specifies additional property names that should raise change notifications when the decorated field changes. Works for both PropertyChanged and PropertyChanging events.
  • [ExcludeFromNotification] - Excludes the decorated field from notification property generation. The field will not have a corresponding public property generated.

Example

using BB84.SourceGenerators.Attributes;

[GenerateNotifications(hasChanged: true)]
public partial class Person
{
    private int _id;

    [AlsoNotify(nameof(FullName))]
    private string _firstName;

    [AlsoNotify(nameof(FullName))]
    private string _lastName;

    private string _fullName;

    [ExcludeFromNotification]
    private string _internalToken;

    private DateTime _createdAt;
    private DateTime? _updatedAt;

    public Person(int id, string firstName, string lastName)
    {
        _id = id;
        _firstName = firstName;
        _lastName = lastName;
        _createdAt = DateTime.UtcNow;
    }

		public string FullName => $"{_firstName} {_lastName}";
}

Generated Code

The generator creates:

  • Public properties for each private field with change notification (unless excluded with [ExcludeFromNotification])
  • Implementation of INotifyPropertyChanged and/or INotifyPropertyChanging interfaces (configurable)
  • PropertyChanged and/or PropertyChanging events
  • RaisePropertyChanged() and/or RaisePropertyChanging() methods (protected virtual for non-sealed classes, private for sealed classes)
  • Additional change notifications for properties specified via [AlsoNotify]
  • Optional HasChanged property (when hasChanged parameter is true)
  • Correct accessibility modifier matching the user's class declaration

Usage Example

var person = new Person(1, "John", "Doe");

// Subscribe to change notifications
person.PropertyChanging += (s, e) => Console.WriteLine($"Property {e.PropertyName} is changing");
person.PropertyChanged += (s, e) => Console.WriteLine($"Property {e.PropertyName} has changed");

// Change FirstName - fires notifications for both FirstName and FullName
person.FirstName = "Jane";
// Output:
// Property FirstName is changing
// Property FullName is changing
// Property FirstName has changed
// Property FullName has changed

// Check if object has been modified (when hasChanged: true)
if (person.HasChanged)
{
    Console.WriteLine("Person has been modified");
}

// Fields with [ExcludeFromNotification] have no generated property
// person.InternalToken  <-- does not exist

// Only INotifyPropertyChanged (no PropertyChanging events)
[GenerateNotifications(propertyChanging: false)]
public partial class LightweightModel
{
    private string _name;
}

// Works correctly with sealed classes
[GenerateNotifications]
public sealed partial class SealedModel
{
    private int _value;
}

3. Abstraction Generator

Generates interface and implementation classes for static classes, making them testable through dependency injection.

Attribute

[GenerateAbstraction(Type targetType, Type abstractionType, Type implementationType, params string[] excludeMethods)]

Parameters:

  • targetType - The static class to generate an abstraction for
  • abstractionType - The interface type to generate
  • implementationType - The implementation class type to generate
  • excludeMethods - Optional array of method names to exclude from generation

Example

using BB84.SourceGenerators.Attributes;

// Generate abstraction for System.IO.File
[GenerateAbstraction(typeof(File), typeof(IFileProvider), typeof(FileProvider))]
public partial class FileProvider
{ }

public partial interface IFileProvider
{ }

Generated Code

The generator creates:

  • A complete interface (IFileProvider) with all public static methods from the target type
  • An implementation class (FileProvider) that implements the interface and delegates to the static class
  • XML documentation comments using <inheritdoc>

Usage Example

// In your service
public class DocumentService
{
    private readonly IFileProvider _fileProvider;

    public DocumentService(IFileProvider fileProvider)
        => _fileProvider = fileProvider;

    public string ReadDocument(string path)
        => _fileProvider.ReadAllText(path);
}

// In your DI container setup
services.AddSingleton<IFileProvider, FileProvider>();

// In tests, you can mock IFileProvider
var mockFileProvider = new Mock<IFileProvider>();
mockFileProvider.Setup(x => x.ReadAllText(It.IsAny<string>())).Returns("test content");

4. INI File Generator

Generates static Read and Write methods and an instance Load method for classes, enabling compile-time INI file serialization, deserialization, and instance-to-instance value copying based on decorated properties.

Attributes

[GenerateIniFile(StringComparison stringComparison = StringComparison.OrdinalIgnoreCase, string sectionDelimiter = ".")]
[GenerateIniFileSection(string? name = null)]
[GenerateIniFileValue(string? name = null)]

Parameters:

  • GenerateIniFile - Marks a class for INI file code generation. The optional stringComparison parameter controls how section and key names are compared during deserialization (default: OrdinalIgnoreCase). The optional sectionDelimiter parameter specifies the delimiter for nested section names (default: "."). The optional SerializeComments property, when set to true, includes XML documentation <summary> comments from section and value properties as INI comment lines (prefixed with ; ) in the generated Write output (default: false)
  • GenerateIniFileSection - Marks a property as an INI file section. The optional name parameter specifies the section name; if omitted, the property name is used. Can also be applied to properties within section types to create nested sections
  • GenerateIniFileValue - Marks a property as a key-value pair within an INI section. The optional name parameter specifies the key name; if omitted, the property name is used

Supported Value Types: string, int, long, float, double, bool, decimal, DateTime, Guid, TimeSpan, DateTimeOffset

Collection Support: List<T> and T[] are supported for any of the above value types. Values are serialized and deserialized as comma-separated strings.

Example

using BB84.SourceGenerators.Attributes;

[GenerateIniFile]
public partial class AppConfig
{
    [GenerateIniFileSection("General")]
    public GeneralSection General { get; set; }

    [GenerateIniFileSection("Database")]
    public DatabaseSection Database { get; set; }
}

public class GeneralSection
{
    [GenerateIniFileValue("AppName")]
    public string ApplicationName { get; set; }

    [GenerateIniFileValue("Version")]
    public int ApplicationVersion { get; set; }

    [GenerateIniFileValue("Debug")]
    public bool IsDebug { get; set; }
}

public class DatabaseSection
{
    [GenerateIniFileValue("Server")]
    public string Server { get; set; }

    [GenerateIniFileValue("Port")]
    public int Port { get; set; }

    [GenerateIniFileValue("Timeout")]
    public double Timeout { get; set; }
}

Generated Methods

The generator creates the following methods on the decorated class:

  • Read(string content) - Static method that parses an INI file string and returns a deserialized instance
  • TryRead(string content, out T? result) - Static method that attempts to parse an INI file string, returning true on success or false on failure without throwing exceptions
  • Write(TClass instance) - Static method that serializes an instance into an INI file string
  • ReadAsync(TextReader reader) - Static async method that reads and deserializes INI content from a TextReader
  • ReadAsync(Stream stream) - Static async method that reads and deserializes INI content from a Stream
  • WriteAsync(TClass instance, TextWriter writer) - Static async method that serializes an instance and writes it to a TextWriter
  • WriteAsync(TClass instance, Stream stream) - Static async method that serializes an instance and writes it to a Stream
  • Load(TClass other) - Instance method that copies all section/value properties from another instance into this instance (null-safe per section)

Usage Example

// Reading an INI file
string iniContent = File.ReadAllText("config.ini");
AppConfig config = AppConfig.Read(iniContent);

Console.WriteLine(config.General.ApplicationName); // "MyApp"
Console.WriteLine(config.Database.Port);   // 5432

// Modifying and writing back
config.General.Debug = false;
config.Database.Timeout = 60.0;

string output = AppConfig.Write(config);
File.WriteAllText("config.ini", output);

// Loading values from another instance
string fileContent = File.ReadAllText("updated.ini");
AppConfig newConfig = AppConfig.Read(fileContent);
config.Load(newConfig); // copies all section values from newConfig into config

// Safe parsing with TryRead (no exceptions)
if (AppConfig.TryRead(iniContent, out AppConfig? parsed))
{
    Console.WriteLine(parsed.General.ApplicationName);
}
else
{
    Console.WriteLine("Failed to parse INI content");
}

// Async reading from a stream
using FileStream fs = File.OpenRead("config.ini");
AppConfig asyncConfig = await AppConfig.ReadAsync(fs);

// Async writing to a stream
using FileStream outFs = File.Create("output.ini");
await AppConfig.WriteAsync(asyncConfig, outFs);

Output:

[General]
AppName=MyApp
Version=1
Debug=False

[Database]
Server=localhost
Port=5432
Timeout=60

Case-Sensitive Matching:

By default, section and key names are compared case-insensitively (OrdinalIgnoreCase). To use case-sensitive matching:

[GenerateIniFile(StringComparison.Ordinal)]
public partial class StrictConfig
{
    [GenerateIniFileSection("General")]
    public GeneralSection General { get; set; }
}

Nested Sections:

The generator supports nested sections by applying [GenerateIniFileSection] to properties within section types. Nested sections are represented with dotted names (e.g., [Server.Database]), and nesting is supported up to 8 levels deep:

[GenerateIniFile]
public partial class Config
{
    [GenerateIniFileSection]
    public ServerSection Server { get; set; }
}

public class ServerSection
{
    [GenerateIniFileValue]
    public string Host { get; set; }

    [GenerateIniFileSection]
    public DatabaseSection Database { get; set; }
}

public class DatabaseSection
{
    [GenerateIniFileValue]
    public int Port { get; set; }
}

This produces:

[Server]
Host=localhost

[Server.Database]
Port=5432

To use a custom delimiter (e.g., /):

[GenerateIniFile(sectionDelimiter: "/")]
public partial class Config { ... }
// Produces: [Server/Database]

Serializing Comments:

When SerializeComments is set to true, XML documentation <summary> comments on section and value properties are emitted as INI comment lines (prefixed with ; ) in the Write output. The Read method automatically skips these comment lines during deserialization:

[GenerateIniFile(SerializeComments = true)]
public partial class AppConfig
{
    /// <summary>
    /// General application settings
    /// </summary>
    [GenerateIniFileSection]
    public GeneralSection General { get; set; }
}

public class GeneralSection
{
    /// <summary>
    /// The display name of the application
    /// </summary>
    [GenerateIniFileValue]
    public string AppName { get; set; }

    /// <summary>
    /// The current version number
    /// </summary>
    [GenerateIniFileValue]
    public int Version { get; set; }
}

This produces:

; General application settings
[General]
; The display name of the application
AppName=MyApp
; The current version number
Version=1

Enumeration Support:

Regular enums will be serialized as their string name. Enum flags will be serialized as a space-delimited string of individual flag names.

public enum LogLevel { Debug, Info, Warning, Error }

[Flags]
public enum Permissions { None = 0, Read = 1, Write = 2, Execute = 4 }

public class AppSection
{
    [GenerateIniFileValue]
    public LogLevel Level { get; set; }

    [GenerateIniFileValue]
    public Permissions Perms { get; set; }
}

This produces:

[App]
Level=Warning
Perms=Read Write Execute

Collection Support:

List<T> and T[] properties are serialized as comma-separated values:

public class SettingsSection
{
    [GenerateIniFileValue]
    public List<string>? Tags { get; set; }

    [GenerateIniFileValue]
    public int[]? Ports { get; set; }

    [GenerateIniFileValue]
    public List<Guid>? Identifiers { get; set; }
}

This produces:

[Settings]
Tags=web,api,backend
Ports=8080,8443,9090
Identifiers=d3b07384-d9a0-4e9b-8c12-f7a8b2c1d3e5,a1b2c3d4-e5f6-7890-abcd-ef1234567890

5. Builder Generator

Generates a fluent builder class for classes, providing With{PropertyName}(value) methods for each public settable property and a Build() method that creates the instance.

Attribute

[GenerateBuilder]

Example

using BB84.SourceGenerators.Attributes;

[GenerateBuilder]
public partial class UserProfile
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string? Email { get; set; }
    public int Age { get; set; }
    public bool IsActive { get; set; }
}

Generated Code

The generator creates a {ClassName}Builder class with:

  • A With{PropertyName}(value) fluent method for each public settable property
  • A Build() method that creates the instance via object initializer
  • Proper nullable reference type annotations
  • XML documentation comments

Usage Example

// Create an instance using the fluent builder
UserProfile profile = new UserProfileBuilder()
    .WithId(1)
    .WithName("John Doe")
    .WithEmail("john@example.com")
    .WithAge(30)
    .WithIsActive(true)
    .Build();

// Only set the properties you need - others use default values
UserProfile minimal = new UserProfileBuilder()
    .WithName("Jane Doe")
    .Build();

// Builders can be reused to create multiple instances
var builder = new UserProfileBuilder()
    .WithName("Template User")
    .WithIsActive(true);

UserProfile first = builder.WithId(1).Build();
UserProfile second = builder.WithId(2).Build();

6. ToString Generator

Generates a ToString() override for classes, returning a formatted string containing the class name and all (or selected) public readable property values in the format ClassName { Prop1 = val1, Prop2 = val2 }.

Attribute

[GenerateToString(params string[] excludeProperties)]

Parameters:

  • excludeProperties - Optional list of property names to exclude from the generated ToString() output

Example

using BB84.SourceGenerators.Attributes;

[GenerateToString]
public partial class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public double Price { get; set; }
    public bool InStock { get; set; }
}

// Exclude sensitive or verbose properties
[GenerateToString("PasswordHash", "InternalNotes")]
public partial class User
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string PasswordHash { get; set; }
    public string InternalNotes { get; set; }
}

Generated Code

The generator creates a ToString() override on the partial class that:

  • Includes all public readable properties by default
  • Excludes properties specified in the attribute parameter
  • Formats output as ClassName { Prop1 = val1, Prop2 = val2 }
  • Returns ClassName { } when all properties are excluded

Usage Example

var product = new Product
{
    Id = 1,
    Name = "Widget",
    Price = 9.99,
    InStock = true
};

Console.WriteLine(product.ToString());
// Output: Product { Id = 1, Name = Widget, Price = 9.99, InStock = True }

var user = new User
{
    Id = 42,
    Name = "John Doe",
    PasswordHash = "abc123hash",
    InternalNotes = "VIP customer"
};

Console.WriteLine(user.ToString());
// Output: User { Id = 42, Name = John Doe }

7. Validator Generator

Generates a Validate() method for classes, scanning properties for data annotation attributes at compile time and emitting direct validation checks. This replaces runtime reflection-based Validator.TryValidateObject() with zero-overhead generated code.

Attribute

[GenerateValidator]

Supported Data Annotations:

  • [Required] - Validates that the property is not null (or not null/empty for strings)
  • [Range(min, max)] - Validates that a numeric value falls within a specified range, or that a collection has between min and max elements
  • [StringLength(max, MinimumLength = min)] - Validates string length within bounds
  • [MinLength(length)] - Validates minimum length of a string or collection
  • [MaxLength(length)] - Validates maximum length of a string or collection
  • [RegularExpression(pattern)] - Validates that a string matches a regex pattern
  • [EmailAddress] - Validates that a string is a valid email address format
  • [Url] - Validates that a string is a valid fully-qualified HTTP, HTTPS, or FTP URL
  • [Phone] - Validates that a string is a valid phone number format
  • [CreditCard] - Validates that a string passes the Luhn algorithm (credit card check)
  • [Compare("OtherProperty")] - Validates that the property value matches another property's value
  • Custom ValidationAttribute subclasses - Any attribute inheriting from ValidationAttribute is automatically detected and validated via IsValid() delegation

IValidatableObject Integration:

When a class implements IValidatableObject, the generated Validate() method automatically calls IValidatableObject.Validate(ValidationContext) and merges the results into the error dictionary.

Constraints:

  • The attribute cannot be applied to abstract classes. A compile-time error (BB84SG0001) will be emitted if used on an abstract class.
  • When a class inherits from a base class, the generated validator includes validation for all public properties from the entire inheritance hierarchy.

Example

using System.ComponentModel.DataAnnotations;
using BB84.SourceGenerators.Attributes;

[GenerateValidator]
public partial class UserRegistration
{
    [Required]
    [StringLength(100, MinimumLength = 2)]
    public string? Name { get; set; }

    [Required]
    [RegularExpression(@"^[^@\s]+@[^@\s]+\.[^@\s]+$")]
    public string? Email { get; set; }

    [Range(18, 120)]
    public int Age { get; set; }

    [Required]
    [MinLength(8)]
    [MaxLength(128)]
    public string? Password { get; set; }

    [Range(1, 10)]
    public List<int>? Skills { get; set; }
}

Generated Code

The generator creates the following methods on the partial class:

  • Validate() - Returns a Dictionary<string, List<string>> where each key is a property name and the value is a list of validation error messages for that property. An empty dictionary indicates a valid instance.
  • Validate(string propertyName) - Returns a List<string> of validation error messages for the specified property. An empty list indicates the property is valid.

Both methods contain direct if-checks for each data annotation rule and support custom error messages via ErrorMessage property.

Usage Example

var registration = new UserRegistration
{
    Name = "J",
    Email = "not-an-email",
    Age = 15,
    Password = "short"
};

Dictionary<string, List<string>> errors = registration.Validate();

if (errors.Count > 0)
{
    foreach (KeyValuePair<string, List<string>> entry in errors)
    {
        Console.WriteLine($"{entry.Key}:");
        foreach (string error in entry.Value)
        {
            Console.WriteLine($"  - {error}");
        }
    }
    // Output:
    // Name:
    //   - The field Name must be a string with a minimum length of 2 and a maximum length of 100.
    // Email:
    //   - The field Email must match the regular expression '^[^@\s]+@[^@\s]+\.[^@\s]+$'.
    // Age:
    //   - The field Age must be between 18 and 120.
    // Password:
    //   - The field Password must be a string or collection with a minimum length of 8.
}
else
{
    Console.WriteLine("Registration is valid!");
}

// Validate a single property
List<string> nameErrors = registration.Validate("Name");
if (nameErrors.Count > 0)
{
    Console.WriteLine($"Name errors: {string.Join(", ", nameErrors)}");
}

// Custom error messages
[GenerateValidator]
public partial class LoginModel
{
    [Required(ErrorMessage = "Username is required.")]
    public string? Username { get; set; }

    [Required(ErrorMessage = "Password cannot be empty.")]
    [MinLength(6, ErrorMessage = "Password must be at least 6 characters.")]
    public string? Password { get; set; }
}

// Cross-property validation with [Compare]
[GenerateValidator]
public partial class RegistrationModel
{
    [Required]
    [EmailAddress]
    public string? Email { get; set; }

    [Required]
    [MinLength(8)]
    public string? Password { get; set; }

    [Compare(nameof(Password), ErrorMessage = "Passwords do not match.")]
    public string? ConfirmPassword { get; set; }

    [Url]
    public string? Website { get; set; }

    [Phone]
    public string? PhoneNumber { get; set; }

    [CreditCard]
    public string? PaymentCard { get; set; }
}

// Custom ValidationAttribute subclasses
public sealed class EvenNumberAttribute : ValidationAttribute
{
    public override bool IsValid(object? value)
        => value is int n && n % 2 == 0;
}

[GenerateValidator]
public partial class CustomAttrModel
{
    [EvenNumber(ErrorMessage = "Must be even.")]
    public int Value { get; set; }
}

// IValidatableObject integration
[GenerateValidator]
public partial class DateRangeModel : IValidatableObject
{
    [Required]
    public DateTime Start { get; set; }

    [Required]
    public DateTime End { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext context)
    {
        if (End <= Start)
            yield return new ValidationResult("End must be after Start.", new[] { nameof(End) });
    }
}

8. Equality Generator

Generates Equals(object), Equals(T), GetHashCode(), operator ==, and operator != for classes, implementing IEquatable<T>. This replaces tedious and error-prone manual equality implementations with zero-overhead generated code.

Attribute

[GenerateEquality(params string[] excludeProperties)]

Parameters:

  • excludeProperties - Optional list of property names to exclude from the generated equality comparison

Example

using BB84.SourceGenerators.Attributes;

[GenerateEquality]
public partial class Product
{
    public int Id { get; set; }
    public string? Name { get; set; }
    public double Price { get; set; }
    public bool InStock { get; set; }
}

// Exclude volatile or non-significant properties
[GenerateEquality("CachedHash", "LastAccessed")]
public partial class User
{
    public int Id { get; set; }
    public string? Name { get; set; }
    public string? CachedHash { get; set; }
    public DateTime LastAccessed { get; set; }
}

Generated Code

The generator creates the following members on the partial class:

  • Equals(object?) override — delegates to the typed Equals(T?) method
  • Equals(T?) implementing IEquatable<T> — compares all (or selected) public properties
  • GetHashCode() override — produces a consistent hash from all (or selected) public properties
  • operator == and operator != — delegates to Equals

Usage Example

var a = new Product { Id = 1, Name = "Widget", Price = 9.99, InStock = true };
var b = new Product { Id = 1, Name = "Widget", Price = 9.99, InStock = true };
var c = new Product { Id = 2, Name = "Gadget", Price = 19.99, InStock = false };

// Typed equality
bool equal = a.Equals(b);    // true
bool different = a.Equals(c); // false

// Operator equality
bool op = a == b;  // true
bool neq = a != c; // true

// Consistent hash codes
bool sameHash = a.GetHashCode() == b.GetHashCode(); // true

// Works with collections that use equality
var set = new HashSet<Product> { a };
bool contains = set.Contains(b); // true

// IEquatable<T> is implemented
IEquatable<Product> equatable = a;

9. Cloneable Generator

Generates Clone() and DeepClone() methods for classes and structs, implementing ICloneable. The Clone() method performs a shallow copy, while DeepClone() recursively deep clones reference-type properties that are also marked with [GenerateCloneable], and creates independent copies of collection properties (List<T>, Dictionary<K,V>, T[], ImmutableArray<T>, read-only collections).

Attribute

[GenerateCloneable(params string[] excludeProperties)]

Parameters:

  • excludeProperties - Optional list of property names to exclude from the generated clone methods

Supported Types:

  • Classes (partial class)
  • Structs (partial struct)

Collection Deep Cloning:

The following collection types are automatically deep copied during DeepClone():

  • List<T> — creates a new list; if T is marked with [GenerateCloneable], elements are recursively deep cloned
  • Dictionary<TKey, TValue> — creates a new dictionary; if TValue is marked with [GenerateCloneable], values are recursively deep cloned
  • T[] (arrays) — creates a new array copy; if T is marked with [GenerateCloneable], elements are recursively deep cloned
  • ImmutableArray<T> — value type, copied directly; if T is marked with [GenerateCloneable], elements are recursively deep cloned into a new ImmutableArray<T>
  • ReadOnlyCollection<T>, IReadOnlyList<T>, IReadOnlyCollection<T> — creates a new read-only collection; if T is marked with [GenerateCloneable], elements are recursively deep cloned

Example

using BB84.SourceGenerators.Attributes;

[GenerateCloneable]
public partial class UserProfile
{
    public int Id { get; set; }
    public string? Name { get; set; }
    public double Score { get; set; }
    public Address? Address { get; set; }
    public List<string>? Tags { get; set; }
    public Dictionary<string, int>? Scores { get; set; }
}

[GenerateCloneable]
public partial class Address
{
    public string? Street { get; set; }
    public string? City { get; set; }
}

// Exclude specific properties from cloning
[GenerateCloneable("CacheToken")]
public partial class Session
{
    public int Id { get; set; }
    public string? User { get; set; }
    public string? CacheToken { get; set; }
}

// Struct support
[GenerateCloneable]
public partial struct Coordinate
{
    public double X { get; set; }
    public double Y { get; set; }
    public List<string>? Labels { get; set; }
}

Generated Code

The generator creates the following members on the partial class or struct:

  • Clone() — creates a shallow copy by assigning all included public read/write properties (for structs, copies the value)
  • DeepClone() — creates a deep copy; reference-type properties marked with [GenerateCloneable] are recursively deep cloned, collection properties are independently copied, while other properties are shallow copied
  • Explicit ICloneable.Clone() — delegates to DeepClone()

Usage Example

var original = new UserProfile
{
    Id = 1,
    Name = "John Doe",
    Score = 95.5,
    Address = new Address { Street = "123 Main St", City = "Springfield" },
    Tags = new List<string> { "admin", "user" },
    Scores = new Dictionary<string, int> { ["math"] = 95, ["science"] = 88 }
};

// Shallow clone - Address and collections are the same reference
UserProfile shallow = original.Clone();
shallow.Address.City = "Shelbyville";
Console.WriteLine(original.Address.City); // "Shelbyville" (shared reference)

// Deep clone - Address and collections are new independent instances
UserProfile deep = original.DeepClone();
deep.Address.City = "Capital City";
deep.Tags.Add("editor");
deep.Scores["art"] = 72;
Console.WriteLine(original.Address.City); // "Shelbyville" (unaffected)
Console.WriteLine(original.Tags.Count);   // 2 (unaffected)
Console.WriteLine(original.Scores.Count); // 2 (unaffected)

// ICloneable is implemented (delegates to DeepClone)
ICloneable cloneable = original;
object copy = cloneable.Clone();

// Excluded properties are not copied
var session = new Session { Id = 1, User = "admin", CacheToken = "abc123" };
Session clonedSession = session.Clone();
Console.WriteLine(clonedSession.CacheToken); // null

// Struct cloning
var coord = new Coordinate { X = 1.0, Y = 2.0, Labels = new List<string> { "origin" } };
Coordinate clonedCoord = coord.DeepClone();
clonedCoord.Labels.Add("copy");
Console.WriteLine(coord.Labels.Count); // 1 (unaffected)

10. Assembly Information Generator

Generates compile-time constant properties for assembly metadata (title, version, company, copyright, etc.), eliminating the need for runtime reflection via Assembly.GetCustomAttribute<T>().

Attribute

[GenerateAssemblyInformation]

Example

using BB84.SourceGenerators.Attributes;

[GenerateAssemblyInformation]
public partial class AppInfo
{ }

Generated Code

The generator creates public const string properties for each standard assembly attribute:

  • Title — from AssemblyTitleAttribute
  • Description — from AssemblyDescriptionAttribute
  • Company — from AssemblyCompanyAttribute
  • Product — from AssemblyProductAttribute
  • Copyright — from AssemblyCopyrightAttribute
  • Trademark — from AssemblyTrademarkAttribute
  • Configuration — from AssemblyConfigurationAttribute
  • Version — from AssemblyVersionAttribute
  • FileVersion — from AssemblyFileVersionAttribute
  • InformationalVersion — from AssemblyInformationalVersionAttribute

Usage Example

Console.WriteLine(AppInfo.Title);                // "MyApp"
Console.WriteLine(AppInfo.Version);              // "1.0.0.0"
Console.WriteLine(AppInfo.Company);              // "My Company"
Console.WriteLine(AppInfo.Copyright);            // "Copyright © 2025"
Console.WriteLine(AppInfo.InformationalVersion); // "1.0.0+abc123"

11. Singleton Generator

Generates the singleton pattern for classes, providing a static Instance property and a private constructor. Supports thread-safe lazy initialization via Lazy<T> (default) or simple static field initialization. When the class implements an interface, the Instance property is typed as the interface.

Attribute

[GenerateSingleton(bool useLazy = true)]

Parameters:

  • useLazy - When true (default), the singleton is backed by Lazy<T> for thread-safe lazy initialization. When false, a simple static readonly field is used instead.

Constraints:

  • The attribute cannot be applied to classes with required fields or properties. A compile-time error (BB84SG0002) will be emitted if the class contains any required members that prevent parameterless initialization.

Example

using BB84.SourceGenerators.Attributes;

// Lazy singleton (default)
[GenerateSingleton]
public partial class MyService { }

// Non-lazy singleton
[GenerateSingleton(useLazy: false)]
public partial class MyCache { }

// Singleton with interface
[GenerateSingleton]
internal partial class MyService : IMyService { }

Generated Code (Lazy, no interface)

public partial class MyService
{
    private static readonly Lazy<MyService> _lazyInstance = new Lazy<MyService>(() => new MyService());

    /// <summary>
    /// Gets the singleton instance of <see cref="MyService"/>.
    /// </summary>
    public static MyService Instance => _lazyInstance.Value;

    private MyService() { }
}

Generated Code (Lazy, with interface)

internal partial class MyService
{
    private static readonly Lazy<MyService> _lazyInstance = new Lazy<MyService>(() => new MyService());

    /// <summary>
    /// Gets the singleton instance of <see cref="MyService"/> as <see cref="IMyService"/>.
    /// </summary>
    public static IMyService Instance => _lazyInstance.Value;

    private MyService() { }
}

Generated Code (Non-lazy, no interface)

public partial class MyCache
{
    /// <summary>
    /// Gets the singleton instance of <see cref="MyCache"/>.
    /// </summary>
    public static MyCache Instance { get; } = new MyCache();

    private MyCache() { }
}

Usage Example

// Access the singleton instance
MyService service = MyService.Instance;

// With interface typing
IMyService service = MyService.Instance;

// Thread-safe - always returns the same instance
MyService a = MyService.Instance;
MyService b = MyService.Instance;
// a and b are the same reference

12. Decorator Generator

Generates a decorator class that wraps an inner instance, delegates all interface members to it, and exposes them as virtual methods and properties for selective overriding. This replaces runtime proxy generation (e.g., DispatchProxy, Castle.Core DynamicProxy) with zero-overhead compile-time code.

Attribute

[GenerateDecorator]

Features

  • Full interface delegation — properties, methods, and events are all delegated to the inner instance
  • Generic method support — generic interface methods with type parameters and constraints are fully supported
  • Event delegation — interface events are delegated via add/remove accessors
  • Partial method hookspartial void On{MethodName}Executing() and partial void On{MethodName}Executed() methods are generated for each interface method, enabling optional logging/tracing without subclassing

Example

using BB84.SourceGenerators.Attributes;

public interface IMyService
{
    string? Name { get; set; }
    int Count { get; }
    event EventHandler? Changed;
    string GetMessage(string input);
    T Transform<T>(T input) where T : class;
    void DoWork();
}

[GenerateDecorator]
public partial class MyServiceDecorator : IMyService
{ }

Generated Code

The generator creates the following members on the partial class:

  • A constructor accepting the interface type and storing it as a protected field (_inner)
  • Virtual property implementations that delegate to the inner instance
  • Virtual method implementations that delegate to the inner instance
  • Event add/remove accessors that delegate to the inner instance
  • Generic method implementations preserving type parameters and constraints
  • partial void On{MethodName}Executing() and partial void On{MethodName}Executed() declarations for each method, invoked before and after delegation respectively
  • Null check on the constructor parameter

Usage Example

// Basic delegation - all calls forwarded to inner instance
var inner = new MyService();
var decorator = new MyServiceDecorator(inner);

decorator.DoWork();           // delegates to inner.DoWork()
string msg = decorator.GetMessage("Hi"); // delegates to inner.GetMessage("Hi")

// Generic methods work seamlessly
string result = decorator.Transform("hello"); // delegates to inner.Transform<string>("hello")

// Events are delegated
decorator.Changed += (s, e) => Console.WriteLine("Changed!");

// Override specific behavior by inheriting from the decorator
public class LoggingDecorator(IMyService inner) : MyServiceDecorator(inner)
{
    public override string GetMessage(string input)
    {
        Console.WriteLine($"GetMessage called with: {input}");
        return base.GetMessage(input);
    }

    public override string? Name
    {
        get => base.Name?.ToUpperInvariant();
        set => base.Name = value;
    }
}

// Use partial method hooks for logging/tracing without subclassing
[GenerateDecorator]
public partial class TracedServiceDecorator : IMyService
{
    partial void OnGetMessageExecuting()
        => Console.WriteLine("GetMessage is about to be called");

    partial void OnGetMessageExecuted()
        => Console.WriteLine("GetMessage has completed");

    partial void OnDoWorkExecuting()
        => Trace.TraceInformation("DoWork starting");

    partial void OnDoWorkExecuted()
        => Trace.TraceInformation("DoWork completed");
}

// Multiple interfaces are supported
public interface ICalculator
{
    int Calculate(int a, int b);
}

[GenerateDecorator]
public partial class MultiDecorator : IMyService, ICalculator
{ }

// In your DI container
services.AddSingleton<IMyService>(sp =>
    new LoggingDecorator(sp.GetRequiredService<MyService>()));

13. Factory Generator

Generates a factory class that discovers all concrete implementations of a target interface at compile time and emits a Create(string key) method that maps keys to concrete types. This eliminates runtime assembly scanning and manual factory maintenance entirely.

Attributes

[GenerateFactory(Type interfaceType)]
[GenerateFactoryKey(string key)]

Parameters:

  • GenerateFactory - Marks a partial class for factory code generation. The interfaceType parameter specifies the interface whose implementations should be discovered.
  • GenerateFactoryKey - Optional attribute placed on implementation classes to specify a custom key. When not present, the class name is used as the key.

Constraints:

  • The interfaceType must be an interface. A compile-time error (BB84SG0004) will be emitted if a non-interface type is specified.
  • Duplicate factory keys among implementations will produce a compile-time error (BB84SG0005).
  • Only concrete classes with a public parameterless constructor are included.

Example

using BB84.SourceGenerators.Attributes;

public interface IAnimal
{
    string Speak();
}

public class Dog : IAnimal
{
    public string Speak() => "Woof!";
}

public class Cat : IAnimal
{
    public string Speak() => "Meow!";
}

[GenerateFactory(typeof(IAnimal))]
public partial class AnimalFactory
{ }

Generated Code

The generator creates the following static methods on the partial class:

  • Create(string key) - Creates an instance of the target interface based on the specified key using a switch expression. Throws ArgumentException for unknown keys.
  • GetKeys() - Returns an IEnumerable<string> of all registered factory keys.

Usage Example

// Create instances by type name (default key)
IAnimal dog = AnimalFactory.Create("Dog");
IAnimal cat = AnimalFactory.Create("Cat");

Console.WriteLine(dog.Speak()); // "Woof!"
Console.WriteLine(cat.Speak()); // "Meow!"

// Unknown keys throw ArgumentException
try
{
    AnimalFactory.Create("Fish");
}
catch (ArgumentException ex)
{
    Console.WriteLine(ex.Message); // "No implementation registered for key 'Fish'."
}

// Enumerate all registered keys
foreach (string key in AnimalFactory.GetKeys())
{
    Console.WriteLine(key); // "Dog", "Cat"
}

// Use custom keys with [GenerateFactoryKey]
public interface IVehicle
{
    string Type { get; }
}

[GenerateFactoryKey("sedan")]
public class Car : IVehicle
{
    public string Type => "Car";
}

[GenerateFactoryKey("pickup")]
public class Truck : IVehicle
{
    public string Type => "Truck";
}

[GenerateFactory(typeof(IVehicle))]
public partial class VehicleFactory
{ }

// Create using custom keys
IVehicle vehicle = VehicleFactory.Create("sedan");
Console.WriteLine(vehicle.Type); // "Car"

14. Disposable Generator

Generates the complete dispose pattern for classes, implementing IDisposable and optionally IAsyncDisposable. Fields marked with [DisposeResource] are automatically disposed with configurable ordering.

Attributes

[GenerateDisposable(bool generateFinalizer = false, bool async = false)]
[DisposeResource(int order = 0)]

Parameters:

  • GenerateDisposable:
    • generateFinalizer - When true, generates a finalizer that calls Dispose(false)
    • async - When true, additionally implements IAsyncDisposable with DisposeAsync() and DisposeAsyncCore()
  • DisposeResource:
    • order - Disposal order. Non-zero values are disposed first (ascending), then zero-order fields in source declaration order

Constraints:

  • GenerateDisposable can only be applied to classes.
  • DisposeResource can only be applied to fields.
  • IAsyncDisposable is only implemented when async: true is specified. A compile-time error (BB84SG0003) will be emitted if the framework does not support IAsyncDisposable and async: true is used.

Example

using BB84.SourceGenerators.Attributes;

[GenerateDisposable]
public partial class ConnectionManager
{
    [DisposeResource]
    private Stream _stream;

    [DisposeResource(order: 1)]
    private DbConnection _connection;

    private string _name; // not disposed (no attribute)

    public ConnectionManager(Stream stream, DbConnection connection)
    {
        _stream = stream;
        _connection = connection;
    }
}

Generated Code

The generator creates:

  • private bool _disposed guard field
  • Dispose(bool disposing) method (protected virtual for non-sealed, private for sealed classes)
  • Dispose() implementing IDisposable
  • ThrowIfDisposed() guard method
  • Optional finalizer (when generateFinalizer: true)
  • Optional DisposeAsync() and DisposeAsyncCore() (when async: true)
  • Correct accessibility modifier matching the user's class declaration

Usage Example

// Basic usage
var manager = new ConnectionManager(stream, connection);
// ... use resources ...
manager.Dispose(); // _connection disposed first (order 1), then _stream (order 0)

// With finalizer for unmanaged resources
[GenerateDisposable(generateFinalizer: true)]
public partial class NativeResourceWrapper
{
    [DisposeResource]
    private SafeHandle _handle;
}

// With async disposal
[GenerateDisposable(async: true)]
public partial class AsyncService
{
    [DisposeResource]
    private HttpClient _client;

    [DisposeResource(order: 1)]
    private DbConnection _connection;
}

await using var service = new AsyncService(client, connection);

// Sealed classes work correctly (private methods instead of protected virtual)
[GenerateDisposable]
public sealed partial class SealedResource
{
    [DisposeResource]
    private Stream _stream;
}

// Ordered disposal - dispose writer before underlying stream
[GenerateDisposable]
public partial class FileProcessor
{
    [DisposeResource(order: 2)]
    private Stream _stream;

    [DisposeResource(order: 1)]
    private StreamWriter _writer;
}

15. AutoMapper Generator

Generates property-to-property mapping implementations for partial methods at compile time. Decorated methods automatically map matching properties from the source parameter type to the return type, eliminating manual mapping boilerplate and runtime reflection.

Attributes

[GenerateAutoMapper]
[PropertyMapping(string sourceProperty, string targetProperty)]

Parameters:

  • GenerateAutoMapper - Marks a partial method for automatic mapping code generation. The method must have exactly one parameter (source) and a non-void return type (target).
  • PropertyMapping - Specifies a custom property name mapping between source and target types. Can be applied multiple times for multiple custom mappings.

Constraints:

  • The method must be partial and defined in a partial class
  • The method must have exactly one parameter (the source type)
  • The method must have a non-void return type (the target type)
  • Unmapped target properties (no matching source property) produce a compile-time warning
  • Type-mismatched properties (incompatible types) produce a compile-time warning

Example

using BB84.SourceGenerators.Attributes;

public class UserEntity
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
    public DateTime CreatedAt { get; set; }
}

public class UserDto
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
    public string DisplayName { get; set; }
}

public static partial class UserMapper
{
    [GenerateAutoMapper]
    [PropertyMapping("Name", "DisplayName")]
    public static partial UserDto ToDto(UserEntity entity);
}

Generated Code

The generator creates a mapping implementation that:

  • Maps all properties with matching names and compatible types automatically
  • Applies custom property mappings specified via [PropertyMapping]
  • Handles nullable source parameters with ArgumentNullException guards
  • Uses null-forgiving operators where needed (nullable source to non-nullable target properties)
  • Reports compile-time diagnostics for unmapped or type-mismatched properties

Usage Example

var entity = new UserEntity
{
    Id = 1,
    Name = "John Doe",
    Email = "john@example.com",
    CreatedAt = DateTime.UtcNow
};

// Generated method maps properties automatically
UserDto dto = UserMapper.ToDto(entity);

Console.WriteLine(dto.Id);          // 1
Console.WriteLine(dto.Name);        // "John Doe"
Console.WriteLine(dto.Email);       // "john@example.com"
Console.WriteLine(dto.DisplayName); // "John Doe" (mapped from Name)

// Works with instance methods too
public partial class OrderMapper
{
    [GenerateAutoMapper]
    public partial OrderDto MapOrder(OrderEntity order);
}

// Nullable source parameter generates null check
public static partial class SafeMapper
{
    [GenerateAutoMapper]
    public static partial UserDto ToDto(UserEntity? entity);
    // Throws ArgumentNullException if entity is null
}

Requirements

  • .NET Standard 2.0 or higher
  • C# 7.3 or higher
  • Supports .NET Framework 4.7.2+, .NET Core 2.0+, .NET 5+, .NET 6+, .NET 7+, .NET 8+

Performance Benefits

Enum Extensions

The generated enum extension methods provide significant performance improvements over reflection-based Enum methods:

  • ToStringFast() - Avoids boxing and uses switch expressions
  • IsDefinedFast() - Compile-time switch instead of runtime reflection
  • GetNamesFast()/GetValuesFast() - Returns pre-allocated arrays instead of reflection

Notification Properties

  • Generates optimized property setters with inline equality checks
  • Avoids reflection overhead of PropertyChanged.Fody or similar tools
  • Compile-time code generation means zero runtime overhead
  • Able to generate additional notifications without runtime attribute scanning
  • Also provides fine-grained control over generated properties

INI File Serialization

  • Generates direct string parsing and formatting code at compile time
  • Avoids runtime reflection or third-party INI parsing libraries
  • Uses CultureInfo.InvariantCulture for consistent cross-platform formatting
  • Supports Guid, TimeSpan, DateTimeOffset, and collection types (List<T>, T[])
  • Provides TryRead for safe parsing without exceptions
  • Provides async ReadAsync/WriteAsync for non-blocking I/O with Stream and TextReader/TextWriter

Builder Pattern

  • Generates a complete fluent builder class at compile time
  • Eliminates hand-written builder boilerplate that must be kept in sync with the target class
  • Replaces reflection-based or expression-tree-based builder libraries with zero-overhead generated code
  • Full nullable reference type support for type-safe builder APIs

ToString Generation

  • Generates a direct property-formatting ToString() override at compile time
  • Replaces runtime reflection approaches (typeof(T).GetProperties().Select(...)) with zero-overhead generated code
  • Automatically stays in sync with the class definition - no manual maintenance required
  • Supports property exclusion for sensitive or verbose fields

Validation

  • Generates direct if-checks at compile time for each data annotation rule
  • Replaces Validator.TryValidateObject() which uses TypeDescriptor and reflection at runtime
  • Provides compile-time discovery of validation attributes - no runtime attribute scanning
  • Supports custom error messages for localization and user-friendly feedback
  • Supports [EmailAddress], [Url], [Phone], [CreditCard], and [Compare] attributes
  • Automatically detects custom ValidationAttribute subclasses and delegates to IsValid()
  • Integrates with IValidatableObject for complex cross-property validation logic

Equality

  • Generates correct Equals, GetHashCode, ==, and != implementations at compile time
  • Eliminates tedious and error-prone manual equality boilerplate
  • Replaces runtime reflection approaches with zero-overhead generated code
  • Properly handles null references and value type properties
  • Supports property exclusion for volatile or non-significant fields

Cloneable

  • Generates Clone() and DeepClone() methods at compile time with zero runtime overhead
  • Replaces MemberwiseClone (shallow only), serialization round-trips (slow), and manual clone implementations (error-prone)
  • Recursively deep clones reference-type properties marked with [GenerateCloneable]
  • Automatically deep copies collection properties (List<T>, Dictionary<K,V>, T[], ImmutableArray<T>, read-only collections)
  • Deep clones collection elements when they are marked with [GenerateCloneable]
  • Supports both classes and structs
  • Implements ICloneable for framework compatibility
  • Supports property exclusion for transient or computed fields

Assembly Information

  • Generates const string properties at compile time for all standard assembly attributes
  • Eliminates runtime reflection via Assembly.GetCustomAttribute<T>()
  • Constants can be used in attribute arguments, switch expressions, and other compile-time contexts
  • Automatically stays in sync with project file properties (<AssemblyTitle>, <Version>, etc.)

Singleton

  • Generates thread-safe singleton pattern at compile time with zero boilerplate
  • Supports both Lazy<T>-backed (default) and simple static field initialization
  • Automatically types Instance as the implemented interface when present
  • Compile-time validation prevents usage on classes with required members
  • Replaces manual singleton implementations that are tedious and error-prone

Decorator

  • Generates full interface delegation at compile time with zero runtime overhead
  • Replaces DispatchProxy, Castle.Core DynamicProxy, and manual decorator implementations
  • All generated members are virtual, enabling selective behavior overriding
  • Supports multiple interfaces on a single decorator class
  • Fully supports generic interface methods with type parameters and constraints
  • Delegates interface events via add/remove accessors
  • Generates partial void hooks (On{Method}Executing/On{Method}Executed) for optional logging/tracing without subclassing
  • Automatically stays in sync with interface changes — no manual maintenance required
  • Constructor injection of the inner instance with null checking

Factory

  • Discovers all concrete implementations of a target interface at compile time
  • Eliminates runtime assembly scanning via Assembly.GetTypes() and reflection
  • Generates a Create(string key) method using switch expressions for zero-overhead dispatch
  • Supports custom keys via [GenerateFactoryKey] attribute
  • Compile-time detection of duplicate keys prevents runtime surprises
  • Automatically stays in sync as implementations are added or removed

Disposable

  • Generates the complete dispose pattern at compile time with zero boilerplate
  • Replaces error-prone manual IDisposable implementations (missed fields, wrong guard logic, missing GC.SuppressFinalize)
  • Supports configurable disposal ordering via [DisposeResource(order)] for expressing resource dependencies
  • Optionally generates IAsyncDisposable with DisposeAsync() and DisposeAsyncCore()
  • Sealed class support with correct method visibility (private vs protected virtual)
  • Optional finalizer generation for classes with unmanaged resources

AutoMapper

  • Generates property-to-property mapping code at compile time with zero runtime overhead
  • Replaces manual mapping boilerplate and runtime reflection-based mapping libraries
  • Automatically matches properties by name and validates type compatibility at compile time
  • Supports custom property name mappings via [PropertyMapping] attribute
  • Compile-time diagnostics for unmapped and type-mismatched properties
  • Handles nullable source parameters with automatic null guards

How It Works

Source generators run during compilation and generate additional C# source files that are compiled alongside your code. This means:

  • Zero runtime overhead - All code is generated at compile time
  • IntelliSense support - Generated code appears in Visual Studio IntelliSense
  • Debuggable - You can step through generated code during debugging
  • No reflection - Generated code uses direct method calls

Contributing

Contributions are welcome! If you have an idea for a new feature, improvement, or bug fix, please follow these steps:

  1. Have a look at the Issues to see if your idea has already been discussed.
  2. If you want to work on an existing issue, please comment on the issue to let others know you're working on it.
  3. Fork the repository and create a new branch for your contribution.
  4. Make your changes and commit them with clear and descriptive messages.
  5. Push your changes to your forked repository and submit a pull request to the main repository.

Code of Conduct

We expect all contributors to adhere to the Code of Conduct.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Author

Robert Peter Meyer (BoBoBaSs84)

Changelog

See releases for version history and changelog.

About

A collection of C# source generators that automatically generate boilerplate code at compile time, reducing manual coding and improving code maintainability.

Topics

Resources

License

Code of conduct

Stars

Watchers

Forks

Contributors

Languages