C#
06 / 07

Reflection, Attributes & Source Generators

C#: Reflection, Attributes & Source Generators

Custom Attributes

// Define a custom attribute
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method,
    AllowMultiple = false, Inherited = true)]
public class AuditAttribute : Attribute
{
    public string Action { get; }
    public bool LogParameters { get; set; } = true;

    public AuditAttribute(string action)
    {
        Action = action;
    }
}

// Apply it
[Audit("UserService")]
public class UserService
{
    [Audit("CreateUser", LogParameters = false)]
    public async Task<User> CreateAsync(CreateUserDto dto) { }
}

// Built-in attributes you use daily
[Obsolete("Use NewMethod instead", error: false)]
[Serializable]
[JsonPropertyName("user_id")]
[Required, MaxLength(100)]
[HttpGet, Route("api/users")]
[Authorize(Roles = "admin")]

Reflection

using System.Reflection;

// Inspect types at runtime
Type type = typeof(UserService);
Type runtimeType = someObject.GetType();
Type byName = Type.GetType("MyApp.Services.UserService, MyApp");

// Properties
var props = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var prop in props)
{
    var value = prop.GetValue(someObject);
    prop.SetValue(someObject, newValue);
}

// Methods
var method = type.GetMethod("CreateAsync");
var result = method!.Invoke(instance, new object[] { dto });

// Attributes
var audit = type.GetCustomAttribute<AuditAttribute>();
if (audit is not null)
    Console.WriteLine(audit.Action);

// Check all methods with [Audit]
var auditedMethods = type.GetMethods()
    .Where(m => m.IsDefined(typeof(AuditAttribute), inherit: true))
    .ToList();

// Create instance dynamically
var instance = Activator.CreateInstance(type, arg1, arg2);
var generic = Activator.CreateInstance(typeof(Repository<>).MakeGenericType(entityType));

// Performance: cache reflected members — reflection is slow on first call
// Use cached delegates or compiled expressions for hot paths:

Expression Trees

// Expression<Func<T,R>> — code as data (inspectable + translatable)
// Used by EF Core to translate LINQ to SQL

Expression<Func<User, bool>> filter = u => u.IsActive && u.Age > 18;

// Inspect the tree
var body = (BinaryExpression)filter.Body;  // &&
var left = (MemberExpression)body.Left;    // u.IsActive
Console.WriteLine(left.Member.Name);       // "IsActive"

// Compile to delegate when needed
var func = filter.Compile();
bool result = func(someUser);

// Build expressions programmatically (for dynamic queries)
var param = Expression.Parameter(typeof(User), "u");
var prop = Expression.Property(param, "Name");
var value = Expression.Constant("Alice");
var eq = Expression.Equal(prop, value);
var lambda = Expression.Lambda<Func<User, bool>>(eq, param);
// u => u.Name == "Alice"

// Use in EF Core
var users = await _db.Users.Where(lambda).ToListAsync();

Source Generators

Source Generators (Roslyn) run at compile time and generate additional C# code. They eliminate runtime reflection overhead. Used in: System.Text.Json (JsonSerializerContext), EF Core compiled models, Mapster, AutoMapper.

// Using source-generated JSON serialization (zero reflection at runtime)
[JsonSerializable(typeof(User))]
[JsonSerializable(typeof(List<User>))]
[JsonSourceGenerationOptions(
    PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase,
    WriteIndented = false)]
public partial class AppJsonContext : JsonSerializerContext { }

// Register in ASP.NET Core
builder.Services.ConfigureHttpJsonOptions(opts =>
    opts.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonContext.Default));

// Serialize/deserialize with context (no reflection)
var json = JsonSerializer.Serialize(user, AppJsonContext.Default.User);
var user = JsonSerializer.Deserialize(json, AppJsonContext.Default.User);

// Regex source generators (C# 7 / .NET 7+)
[GeneratedRegex(@"^[^@]+@[^@]+\.[^@]+$", RegexOptions.IgnoreCase)]
private static partial Regex EmailRegex();

bool isValid = EmailRegex().IsMatch(email);  // compiled at build time, not runtime

Keep your own version of these notes — editable, searchable, and organised by your stack.

Start free