Skip to content

About

EasyTypeFactory helps you with creating types fluently in .NET

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

26 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

EasyTypeFactory

License: MIT Target: .NET Standard 2.1 Target: .NET 8.0 Target: .NET 9.0 Target: .NET 10.0

EasyTypeFactory is an efficient, high-performance, fluent dynamic type generator for .NET. It streamlines the creation of runtime types, removing the friction and boilerplate of manual System.Reflection.Emit and MSIL generation.

By providing switch-based runtime member dispatch, automatic constructor synthesis, full IEquatable<T> generation, interface implementation, abstract base class inheritance, and custom method emission (via IL or LINQ Expression trees), EasyTypeFactory maximizes runtime throughput and developer productivity.


Table of Contents


Why EasyTypeFactory?

When applications require dynamic schema generation, data transformation proxies, dynamic DTOs, or runtime compilation, developers often face trade-offs between slow reflection-based dynamic objects (ExpandoObject, dictionaries) and complex, error-prone Reflection.Emit code.

EasyTypeFactory delivers practical utility by eliminating this compromise:

  • Maximum Execution Speed: Emits compiled CIL byte code directly into dynamic assemblies.
  • Zero-Reflection Access: Emits jump tables (efficient switch statements) for name-based lookup and indexers rather than relying on slow runtime reflection calls.
  • Immutable Builder API: Builders are thread-safe and reusable, allowing base templates to branch into different types safely.
  • Full Type System Fidelity: Generated types are real .NET types with interfaces, inheritance, constructors, attributes, and operators.

Target Frameworks & Support Policy

EasyTypeFactory is engineered to maximize performance across modern .NET ecosystems while maintaining practical backward compatibility through standard API contracts.

Officially Supported .NET Versions

We officially support the .NET versions that are actively supported by Microsoft (currently .NET 8.0, .NET 9.0, and .NET 10.0).

Lifecycle and EOL Policy

When Microsoft drops official support for a specific .NET version, EasyTypeFactory will continue supporting that version for at least two more months. After this period, support will be dropped.

Target Framework Matrix & API Contracts

Project Target Frameworks Officially Supported? Notes
EasyTypeFactory netstandard2.1, net8.0, net9.0, net10.0 .NET 8, 9, 10 Includes netstandard2.1 as an API contract. netstandard2.0 is not supported due to required reflection and runtime APIs. While netstandard2.1 allows consumption on other compliant runtimes (such as .NET Core 3.0), we do not officially support framework versions other than those actively supported by .NET.
EasyTypeFactory.HyperbeeExpressionCompiler net8.0, net9.0, net10.0 .NET 8, 9, 10 Does not target netstandard2.1 because the underlying dependency Hyperbee.Expressions.Compiler only targets .NET 8, 9, and 10.

Projects in this Repository

Project Description
EasyTypeFactory The core library containing the fluent builder (EasyTypeBuilder), immutable models, and MSIL code emitters for fields, constructors, indexers, getters, setters, and equality.
EasyTypeFactory.HyperbeeExpressionCompiler An extension library providing LINQ Expression tree method compilation on top of Hyperbee.Expressions.Compiler, avoiding manual IL coding for complex custom methods.
EasyTypeFactory.UnitTests Unit tests targeting builder state transitions, equality comparisons, definitions, and individual IL emitter components.
EasyTypeFactory.IntegrationTests Comprehensive end-to-end integration test suite verifying runtime execution, constructor invocations, interface implementations, base classes, and performance characteristics.

NuGet Packages

(Upcoming releases – packages will be available on NuGet shortly)

1. EasyTypeFactory

The core dynamic type builder package. Contains everything needed to generate POCOs, DTOs, dynamic implementations of interfaces, constructors, and IL-based members.

2. EasyTypeFactory.HyperbeeExpressionCompiler

An optional companion package for applications that want to compile C# LINQ Expression trees directly into instance or static methods on dynamic types.

Package Installation

When available on NuGet, install via the .NET CLI:

# Core package
dotnet add package EasyTypeFactory

# Optional expression compiler extension
dotnet add package EasyTypeFactory.HyperbeeExpressionCompiler

Or via the Package Manager Console in Visual Studio / JetBrains Rider:

Install-Package EasyTypeFactory
Install-Package EasyTypeFactory.HyperbeeExpressionCompiler

Feature Overview

  • Fluid, Immutable API: Every .With*() or .Add*() invocation produces a new builder instance, enabling deterministic branching and template reuse.
  • Rich Constructor Synthesis:
    • Parameterless constructor with automatic default value initialization.
    • Minimal constructor requiring only mandatory / non-default fields (emits [SetsRequiredMembers]).
    • Full constructor taking all declared fields in order.
  • High-Performance Name Dispatch:
    • GetByName(string name) / SetByName(string name, object value)
    • Generic GetByName<T>(string name) / SetByName<T>(string name, T value)
    • Type-specialized methods like GetInt32ByName(string name) or SetStringByName(string name, string value)
  • Dictionary-style Indexer: Emits this[string name] with optional read-only mode and case-insensitive matching.
  • Complete Equality Generation: Implements IEquatable<T>, overrides Equals(object) and GetHashCode(), and generates == and != static operators.
  • Polymorphism Support: Implement multiple interfaces (with automatic property mapping) or derive from abstract/concrete base classes.
  • Custom Methods: Low-level ILGenerator emission or high-level LambdaExpression compilation.

How-To Guides

1. Easy: Getting Started

Basic Dynamic Type

Generate a new dynamic type with fields in just a few lines of code:

using EasyTypeFactory;

// 1. Define the type fluently
Type customerType = EasyTypeBuilder.Create("MyApp.Models.Customer", discriminate: false)
    .AddField("Id", typeof(int))
    .AddField("Name", typeof(string))
    .WithParameterlessConstructor()
    .Build();

// 2. Instantiate and use via standard reflection or dynamic
object customer = Activator.CreateInstance(customerType)!;

customerType.GetField("Id")!.SetValue(customer, 101);
customerType.GetField("Name")!.SetValue(customer, "Alice Smith");

Console.WriteLine($"Created {customerType.FullName}");

Fields with Default Values and Constraints

Define default values, read-only (initonly) fields, and required members:

Type orderType = EasyTypeBuilder.Create("MyApp.Models.Order")
    .AddField("OrderId", typeof(Guid), isReadOnly: true)
    .AddField("CustomerEmail", typeof(string), isRequired: true)
    .AddField("Status", typeof(string), defaultValue: "Pending")
    .AddField("TotalAmount", typeof(decimal), defaultValue: 0.0m)
    .WithParameterlessConstructor()
    .Build();

object order = Activator.CreateInstance(orderType)!;
Console.WriteLine(orderType.GetField("Status")!.GetValue(order)); // Output: Pending

Using Defaults() for Instant Usability

Calling .Defaults() applies standard best practices in one call: it configures parameterless, minimal, and full constructors, along with complete IEquatable<T> equality and comparison operators.

Type productType = EasyTypeBuilder.Create("MyApp.Models.Product")
    .AddField("Sku", typeof(string), isRequired: true)
    .AddField("Price", typeof(double), defaultValue: 9.99)
    .Defaults()
    .Build();

// Uses full constructor (Sku, Price)
object product1 = Activator.CreateInstance(productType, "SKU-1234", 19.99)!;

// Uses minimal constructor (Sku only; Price gets default value 9.99)
object product2 = Activator.CreateInstance(productType, "SKU-1234")!;

// Uses parameterless constructor
object product3 = Activator.CreateInstance(productType)!;

// Structural equality works out of the box
bool areEqual = product1.Equals(product2);

2. Intermediate: Data Access & Operations

Fine-Grained Constructor Management

You can selectively enable or disable specific constructor signatures to maintain precise instantiation contracts:

Type userType = EasyTypeBuilder.Create("MyApp.Models.User")
    .AddField("Id", typeof(int), isRequired: true)
    .AddField("Username", typeof(string), isRequired: true)
    .AddField("Role", typeof(string), defaultValue: "StandardUser")
    .WithParameterlessConstructor()       // ()
    .WithMinimalConstructor()             // (int Id, string Username)
    .WithFullConstructor()                // (int Id, string Username, string Role)
    .Build();

Name-Based Property Access (GetByName & SetByName)

Instead of slow reflection to inspect dynamic objects, configure compiled accessor methods:

Type recordType = EasyTypeBuilder.Create("MyApp.Models.DataRecord")
    .AddField("Score", typeof(int), defaultValue: 100)
    .AddField("Label", typeof(string), defaultValue: "Sample")
    .WithParameterlessConstructor()
    .WithGetByName()      // Emits: object? GetByName(string name)
    .WithSetByName()      // Emits: void SetByName(string name, object? value)
    .Build();

dynamic record = Activator.CreateInstance(recordType)!;

// Fast dynamic invocation or reflection method invocation
record.SetByName("Score", 250);
int score = (int)record.GetByName("Score");
Console.WriteLine($"Updated Score: {score}"); // Output: 250

Strongly Typed and Specialized Accessors

To avoid boxing/unboxing overhead, generate generic or type-specialized accessors:

Type entityType = EasyTypeBuilder.Create("MyApp.Models.Entity")
    .AddField("Count", typeof(int), defaultValue: 42)
    .AddField("Tag", typeof(string), defaultValue: "Beta")
    .WithParameterlessConstructor()
    .WithGenericGetByName()                   // Emits: T GetByName<T>(string name)
    .WithGenericSetByName()                   // Emits: void SetByName<T>(string name, T value)
    .AddGetTypeByName(typeof(int))            // Emits: int GetInt32ByName(string name)
    .AddSetTypeByName("UpdateString", typeof(string)) // Custom named setter
    .DoAddAllGetTypeByNameMethods()           // Emits specialized getters for all field types
    .Build();

String Indexer (this[name])

Expose dictionary-like indexing directly on your compiled dynamic objects:

Type dictionaryLikeType = EasyTypeBuilder.Create("MyApp.Models.FlexObject")
    .AddField("Title", typeof(string), defaultValue: "Untitled")
    .AddField("Views", typeof(int), defaultValue: 0)
    .WithParameterlessConstructor()
    .WithIndexer(setter: true) // Emits: object? this[string name] { get; set; }
    .Build();

dynamic instance = Activator.CreateInstance(dictionaryLikeType)!;
instance["Views"] = 500;
Console.WriteLine(instance["Title"]); // Output: Untitled
Console.WriteLine(instance["Views"]); // Output: 500

Case Sensitivity Configuration

By default, lookups match exact casing. You can switch to case-insensitive field matching for JSON, database mapping, or user input scenarios:

Type configType = EasyTypeBuilder.Create("MyApp.Models.Config")
    .AddField("ApiKey", typeof(string), defaultValue: "secret-123")
    .WithParameterlessConstructor()
    .WithIndexer()
    .CaseInsensitive() // Field lookup matches "apikey", "APIKEY", etc.
    .Build();

dynamic config = Activator.CreateInstance(configType)!;
Console.WriteLine(config["apikey"]); // Output: secret-123

Structural Equality and Comparison Operators

Automatically implement IEquatable<T>, override Object.Equals and Object.GetHashCode, and emit operator overloads (==, !=):

Type pointType = EasyTypeBuilder.Create("MyApp.Models.Point")
    .AddField("X", typeof(int))
    .AddField("Y", typeof(int))
    .WithFullConstructor()
    .WithEquality(operators: true)
    .Build();

dynamic p1 = Activator.CreateInstance(pointType, 10, 20)!;
dynamic p2 = Activator.CreateInstance(pointType, 10, 20)!;
dynamic p3 = Activator.CreateInstance(pointType, 15, 30)!;

Console.WriteLine(p1.Equals(p2)); // true
Console.WriteLine(p1 == p2);      // true
Console.WriteLine(p1 != p3);      // true
Console.WriteLine(p1.GetHashCode() == p2.GetHashCode()); // true

3. Advanced: Architecture & Extensibility

Implementing Interfaces Dynamically

Map interfaces directly to dynamic types. EasyTypeFactory automatically creates the necessary property implementations backed by fields:

public interface IIdentifiable
{
    int Id { get; set; }
    string Code { get; }
}

Type implType = EasyTypeBuilder.Create("MyApp.Models.IdentifiableImpl")
    .AddInterface(typeof(IIdentifiable))
    .AddField("Id", typeof(int))
    .AddField("Code", typeof(string), isReadOnly: true)
    .WithFullConstructor()
    .Build();

IIdentifiable instance = (IIdentifiable)Activator.CreateInstance(implType, 42, "CODE-99")!;
Console.WriteLine($"Id: {instance.Id}, Code: {instance.Code}");

instance.Id = 100; // Modifies backing field

Inheriting from Base Classes

Inherit from existing abstract or concrete classes and implement abstract properties cleanly:

public abstract class EntityBase
{
    public abstract int Id { get; set; }
    public abstract string TenantId { get; }
    
    public void PrintInfo() => Console.WriteLine($"Entity {Id} ({TenantId})");
}

Type derivedType = EasyTypeBuilder.Create("MyApp.Models.ConcreteEntity")
    .WithBaseType(typeof(EntityBase))
    .AddField("Id", typeof(int))
    .AddField("TenantId", typeof(string), isReadOnly: true)
    .WithFullConstructor()
    .Build();

EntityBase entity = (EntityBase)Activator.CreateInstance(derivedType, 1, "US-EAST")!;
entity.PrintInfo(); // Output: Entity 1 (US-EAST)

Emitting Custom Methods with Raw IL

Attach custom member functions directly using ILGenerator:

using System.Reflection;
using System.Reflection.Emit;

Type mathType = EasyTypeBuilder.Create("MyApp.Models.Calculator")
    .WithParameterlessConstructor()
    .AddCustomMethod("Multiply", method => method
        .WithAttributes(MethodAttributes.Public | MethodAttributes.HideBySig)
        .WithReturnType(typeof(int))
        .WithParameterTypes(typeof(int), typeof(int))
        .WithEmitter((il, context) =>
        {
            il.Emit(OpCodes.Ldarg_1); // arg 1: first int
            il.Emit(OpCodes.Ldarg_2); // arg 2: second int
            il.Emit(OpCodes.Mul);     // multiply
            il.Emit(OpCodes.Ret);     // return result
        }))
    .Build();

dynamic calc = Activator.CreateInstance(mathType)!;
int product = calc.Multiply(6, 7);
Console.WriteLine($"Result: {product}"); // Output: 42

Expression-Based Methods (Hyperbee Integration)

With EasyTypeFactory.HyperbeeExpressionCompiler, compile LINQ Expressions into dynamic instance or static methods without writing MSIL:

using System.Linq.Expressions;
using EasyTypeFactory;
using EasyTypeFactory.HyperbeeExpressionCompiler;

// Define an expression: (int a, int b) => a * b + 10
Expression<Func<int, int, int>> expr = (a, b) => a * b + 10;

Type compiledType = EasyTypeBuilder.Create("MyApp.Models.ExpressionCalculator")
    .WithParameterlessConstructor()
    .AddMethodFromExpression("Compute", expr)
    .Build();

dynamic calc = Activator.CreateInstance(compiledType)!;
int result = calc.Compute(5, 4);
Console.WriteLine($"Computed: {result}"); // Output: 30

Custom Dynamic Assembly & Module Management

Persist or isolate generated types into a dedicated ModuleBuilder:

using System.Reflection;
using System.Reflection.Emit;

var assemblyName = new AssemblyName("MyCustomDynamicModuleAssembly");
var dynamicAssembly = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
var dynamicModule = dynamicAssembly.DefineDynamicModule("MainModule");

Type customType = EasyTypeBuilder.Create("MyApp.Models.SharedType")
    .WithModuleBuilder(dynamicModule)
    .AddField("Timestamp", typeof(DateTime))
    .WithParameterlessConstructor()
    .Build();

Console.WriteLine($"Assembly: {customType.Assembly.FullName}");

Builder Immutability & Reusable Type Templates

Because builders are immutable records under the hood, you can define a base template and derive multiple specialized schemas with zero race conditions:

// Define a base template
IEasyTypeBuilder entityTemplate = EasyTypeBuilder.Create()
    .AddField("Id", typeof(Guid), isReadOnly: true)
    .AddField("CreatedAt", typeof(DateTime), defaultValue: DateTime.UtcNow)
    .Defaults();

// Specialize into User type
Type userType = entityTemplate
    .WithName("MyApp.Models.User")
    .AddField("Username", typeof(string))
    .Build();

// Specialize into Order type
Type orderType = entityTemplate
    .WithName("MyApp.Models.Order")
    .AddField("Total", typeof(decimal))
    .Build();

API Quick Reference

Type Configuration

  • EasyTypeBuilder.Create(string? name = null, bool discriminate = true): Creates an empty builder. If discriminate is true, unique suffixes are added to avoid name collisions.
  • Defaults(): Enables parameterless, minimal, and full constructors, equality implementation, and operators.
  • WithModuleBuilder(ModuleBuilder mb) / WithoutModuleBuilder(): Configures target dynamic module.
  • WithBaseType(Type baseType) / WithoutBaseType(): Sets or resets class inheritance.
  • AddInterface(Type interfaceType) / RemoveInterface(Type) / ClearInterfaces(): Manages interface implementations.
  • CaseSensitive() / CaseInsensitive(): Sets member name resolution casing.
  • Build(): Emits and completes the dynamic System.Type.

Field & Property Setup

  • AddField(string name, Type type, bool isReadOnly = false, bool isRequired = false, object? defaultValue = null)
  • RemoveField(string name) / ClearFields()

Constructors

  • WithParameterlessConstructor() / WithoutParameterlessConstructor()
  • WithMinimalConstructor() / WithoutMinimalConstructor()
  • WithFullConstructor() / WithoutFullConstructor()

Accessors & Indexers

  • WithGetByName(string? name = null) / WithoutGetByName()
  • WithGenericGetByName(string? name = null) / WithoutGenericGetByName()
  • AddGetTypeByName(Type type) / AddGetTypeByName(string name, Type type) / DoAddAllGetTypeByNameMethods()
  • WithSetByName(string? name = null) / WithoutSetByName()
  • WithGenericSetByName(string? name = null) / WithoutGenericSetByName()
  • AddSetTypeByName(Type type) / AddSetTypeByName(string name, Type type) / DoAddAllSetTypeByNameMethods()
  • WithIndexer(bool setter = true) / WithoutIndexer()

Custom Methods

  • AddCustomMethod(string name, Func<IEasyTypeMethodBuilder, IEasyTypeMethodBuilder> builder)
  • AddMethodFromExpression(string name, LambdaExpression expression) (via Hyperbee extension)

Performance & Design Notes

  1. Direct CIL Emission: Generated methods compile straight into Common Intermediate Language, achieving execution speeds equivalent to hand-written C# code.
  2. Switch-Based Member Dispatch: Lookups in GetByName, SetByName, and indexers emit IL string equality jump tables rather than invoking slow runtime reflection.
  3. Thread Safety & Immutability: All builder modifications return new immutable configurations, guaranteeing safe concurrent usage across threads.

License

This project is licensed under the MIT License.

About

EasyTypeFactory helps you with creating types fluently in .NET

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Used by

Contributors

Languages