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 runtimeKeep your own version of these notes — editable, searchable, and organised by your stack.
Start free