33. Reflection and Attributes in C#
Level: Advanced
Goal: Inspect and use code information at runtime - read class properties, call methods dynamically, validate with custom rules.
Reflection lets your code look at itself. Example:
How many properties does Student class have?
What is the validation rule for Name field?
Call a method by name at runtime?
This powers frameworks that auto-map database tables to classes, auto-validate forms, auto-inject dependencies.
- What attributes are and why they're useful
- Defining custom attributes with
AttributeUsage - Reflection - reading metadata at runtime
Type,MethodInfo,PropertyInfofor type inspection- Getting properties, methods, fields dynamically
GetCustomAttribute<T>to read attribute metadata- Building validators and ORM mappers with reflection
- Performance implications and best practices
Reflection and Attributes Reference
| Concept | Purpose | Returns | Common Use | Example |
|---|---|---|---|---|
typeof(Type) | Get type object at compile-time | Type | Check type, get metadata | typeof(Student).Name |
Type.GetType(string) | Get type object from string | Type or null | Dynamic type loading | Type.GetType("MyApp.Student") |
PropertyInfo | Metadata about property | Property info | Read/write property values dynamically | prop.GetValue(obj) |
MethodInfo | Metadata about method | Method info | Invoke methods dynamically | method.Invoke(obj, args) |
FieldInfo | Metadata about field | Field info | Read/write field values dynamically | field.GetValue(obj) |
GetProperties() | Get all properties of type | PropertyInfo[] | Enumerate properties (validation, ORM) | typeof(T).GetProperties() |
GetMethods() | Get all methods of type | MethodInfo[] | Enumerate methods, find specific ones | type.GetMethods() |
GetCustomAttribute<T>() | Read attribute from property/method/class | T or null | Retrieve attribute metadata | prop.GetCustomAttribute<RequiredFieldAttribute>() |
Attribute class | Base for all attributes | Base class | Define custom attributes | public class MyAttribute : Attribute |
AttributeUsage | Specify where attribute can be applied | N/A | Control attribute target (Class, Property, Method) | [AttributeUsage(AttributeTargets.Property)] |
Assembly.GetTypes() | Get all types in assembly | Type[] | Scan and load types dynamically | Assembly.GetExecutingAssembly().GetTypes() |
BindingFlags | Filter reflection results | Flags | Include private members, static, etc. | BindingFlags.Public | BindingFlags.IgnoreCase |
Quick Definitions
- Reflection - Code reading metadata about types at runtime
- Attribute - Metadata tag on class/property/method (e.g.,
[Required]) - Custom attribute - Developer-defined attribute with custom logic
- AttributeUsage - Specifies where attribute can be applied
- Type - Represents class/interface/struct at runtime
- PropertyInfo - Metadata about a property
- MethodInfo - Metadata about a method
- GetProperties() - Get all properties of a type
GetCustomAttribute<T>()- Read attribute value- BindingFlags - Filter for public/private/static members
Custom Attributes
// Define custom attributes
[AttributeUsage(AttributeTargets.Property)]
public class RequiredFieldAttribute : Attribute
{
public string ErrorMessage { get; }
public RequiredFieldAttribute(string message = "This field is required.")
{
ErrorMessage = message;
}
}
[AttributeUsage(AttributeTargets.Property)]
public class MaxLengthAttribute : Attribute
{
public int Length { get; }
public MaxLengthAttribute(int length)
{
Length = length;
}
}
[AttributeUsage(AttributeTargets.Property)]
public class RangeAttribute : Attribute
{
public double Min { get; }
public double Max { get; }
public RangeAttribute(double min, double max)
{
Min = min;
Max = max;
}
}
[AttributeUsage(AttributeTargets.Class)]
public class SchoolEntityAttribute : Attribute
{
public string TableName { get; }
public SchoolEntityAttribute(string tableName)
{
TableName = tableName;
}
}
Decorate Student Model
[SchoolEntity("Students")]
public class Student
{
[RequiredField("Student ID is required.")]
public int Id { get; set; }
[RequiredField("Name cannot be empty.")]
[MaxLength(100)]
public string Name { get; set; } = "";
[RequiredField]
[MaxLength(20)]
public string RollNumber { get; set; } = "";
[Range(0, 100)]
public double Percentage { get; set; }
public string? Email { get; set; } // optional - no attribute
}
Reflection - Read Metadata at Runtime
// Inspect type
Type studentType = typeof(Student);
Console.WriteLine($"Type : {studentType.Name}");
Console.WriteLine($"Namespace: {studentType.Namespace}");
Console.WriteLine($"IsClass : {studentType.IsClass}");
// Class attribute
var tableAttr = studentType.GetCustomAttribute<SchoolEntityAttribute>();
Console.WriteLine($"Table : {tableAttr?.TableName}");
// Properties
foreach (var prop in studentType.GetProperties())
{
Console.Write($" {prop.PropertyType.Name,-10} {prop.Name,-20}");
var required = prop.GetCustomAttribute<RequiredFieldAttribute>();
var maxLen = prop.GetCustomAttribute<MaxLengthAttribute>();
if (required != null) Console.Write("[Required]");
if (maxLen != null) Console.Write($"[MaxLen:{maxLen.Length}]");
Console.WriteLine();
}
Generic Validator Using Reflection
When You'll Use This in SMS
SMS uses reflection for validation and ORM operations:
// Auto-validate student form using reflection
public class FormValidator
{
public static List<string> Validate(Student student)
{
var errors = new List<string>();
foreach (var prop in typeof(Student).GetProperties())
{
var required = prop.GetCustomAttribute<RequiredAttribute>();
if (required != null && prop.GetValue(student) == null)
errors.Add($"{prop.Name} is required");
}
return errors;
}
}
// Map Student properties to database columns automatically
var properties = typeof(Student).GetProperties();
foreach (var prop in properties)
{
string columnName = prop.Name;
object value = prop.GetValue(student);
// Build INSERT statement dynamically
}
Real impact: Without reflection = hardcode validator for each model. With reflection = one generic validator for all 12 SMS entities.
Try This Now
- Create custom
[EmailAttribute]validation attribute - Use reflection to read and check it on Student.Email
- Build generic validator for any class with attributes
- Test validator on Student and Teacher models
- Measure reflection performance on 1000 iterations
Reflection explained: attributes, Type/PropertyInfo, custom validators, when NOT to use reflection, ORM mapping basics. Video coming soon. Subscribe to NexCoding YouTube for updates.
When to Use Reflection (and When NOT to)
OK Use Reflection for:
- Validation frameworks (check attributes, validate input)
- ORM/Database mappers (EF Core, Dapper - map properties to columns)
- Dependency injection (inspect constructors, resolve dependencies)
- Serialization (JSON.NET, System.Text.Json - convert objects to JSON)
- Plugin systems (load types dynamically from assemblies)
- Framework code (ASP.NET routing, MVC model binding)
Wrong Avoid Reflection for:
- Performance-critical loops (reflection is slow)
- Simple type checks (use
if (obj is Type)instead of reflection) - Frequent property access (access directly, not via
PropertyInfo) - Real-time systems with strict timing (millisecond delays add up)
// Wrong Slow - reflection in hot loop
for (int i = 0; i < 1000000; i++)
{
var value = typeof(Student)
.GetProperty("Name")
.GetValue(student); // Slow!
}
// OK Fast - direct access
for (int i = 0; i < 1000000; i++)
{
var value = student.Name; // Direct, no reflection
}
// OK Reflection OK - called once at startup
var validator = new ModelValidator();
var errors = validator.Validate(student); // One-time validation, performance OK
Rule: Use reflection for setup/initialization, not tight loops.
Common Mistakes
Wrong Using reflection for simple type checks:
var prop = typeof(Student).GetProperty("Name");
if (prop != null) { } // Overkill
OK Use is operator:
if (obj is Student s) { } // Direct, clear
Wrong Calling reflection in performance-critical loop:
foreach (var student in students) // 10,000 students
{
var type = student.GetType();
var props = type.GetProperties(); // Reflection called 10,000 times!
// ...
}
OK Cache reflection results outside loop:
var props = typeof(Student).GetProperties(); // Once
foreach (var student in students)
{
// Use cached props
}
Wrong Not handling null from GetCustomAttribute:
var attr = prop.GetCustomAttribute<RequiredFieldAttribute>();
var msg = attr.ErrorMessage; // NullReferenceException if not present!
OK Check for null:
var attr = prop.GetCustomAttribute<RequiredFieldAttribute>();
if (attr != null)
var msg = attr.ErrorMessage;
Wrong Using reflection to access private members unnecessarily:
// Bad design - exposes private implementation details
var field = typeof(Student).GetField("_internalId", BindingFlags.NonPublic | BindingFlags.Instance);
var value = field.GetValue(student);
OK Use public API or properties:
var id = student.Id; // Public property, no reflection needed
Wrong Forgetting AttributeUsage specifies targets:
[AttributeUsage(AttributeTargets.Property)]
public class MyAttribute : Attribute { }
[MyAttribute] // ERROR - can only be used on Property, not Class
public class Student { }
OK Specify correct targets:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property)]
public class MyAttribute : Attribute { }
Best Practices
- Cache reflection results - Don't call
GetProperty()in loops - Use
BindingFlagscarefully -Public | Instanceby default, add others if needed - Check null from
GetCustomAttribute- Attribute might not be present - Avoid reflection in hot paths - Move to initialization, cache results
- Document attribute purposes - Explain why attribute is needed, what it validates
- Use generic
GetCustomAttribute<T>- Type-safe, cleaner thanGetCustomAttribute(type) - Profile before optimizing - Reflection might not be bottleneck
- Consider alternative designs - Reflection is powerful but adds complexity
- Validate reflected type names - Typos in
Type.GetType("string")silently fail - Use
AttributeUsage.AllowMultiple- When multiple attributes of same type needed - Test reflection code - Dynamic code is hard to test, ensure edge cases covered
- Document breaking changes - Renaming properties breaks reflection-based code
Reflection = reading type metadata at runtime. Discover properties, methods, attributes dynamically.
Useful for: validation frameworks, ORMs, dependency injection, serialization.
// Reflect on Student type
var type = typeof(Student);
var props = type.GetProperties();
foreach (var prop in props)
{
var attr = prop.GetCustomAttribute<RequiredFieldAttribute>();
if (attr != null)
Console.WriteLine($"{prop.Name} is required");
}
Frameworks use reflection to auto-wire, auto-validate, auto-map. But reflection is slow - don't use in tight loops.
Attributes = metadata markers. Decorate classes/methods/properties with extra info. Accessed via reflection.
// Define
[AttributeUsage(AttributeTargets.Property)]
public class RequiredFieldAttribute : Attribute
{
public string Message { get; }
public RequiredFieldAttribute(string msg) => Message = msg;
}
// Use
public class Student
{
[RequiredField("Name required")]
public string Name { get; set; }
}
// Read via reflection
var attr = typeof(Student)
.GetProperty("Name")
.GetCustomAttribute<RequiredFieldAttribute>();
Console.WriteLine(attr.Message); // "Name required"
AttributeUsage specifies targets: AttributeTargets.Class, .Property, .Method, etc.
EF Core inspects DbSet<T> properties via reflection, maps each property to database column.
public class StudentContext : DbContext
{
public DbSet<Student> Students { get; set; }
}
// EF Core reflects:
// - DbSet<Student> property -> "Students" table
// - Student.Id property -> primary key column
// - Student.Name property -> "Name" column
// - [Key], [Column("alias")], [Required] attributes -> column constraints
Same with ASP.NET Core routing: reflects on Controller classes, discovers action methods, builds routes.
Reflection enables conventions - less boilerplate code.
Reflection is slow. GetProperty() lookup, GetValue() call cost time.
// Slow - reflection called 1,000,000 times
for (int i = 0; i < 1000000; i++)
{
var val = typeof(Student).GetProperty("Name").GetValue(student);
}
// Fast - direct access
for (int i = 0; i < 1000000; i++)
{
var val = student.Name;
}
Typical reflection call = 10-100x slower than direct access.
Solution: Cache reflection results. Use reflection at startup (DI registration), not in request handling.
typeof(): Compile-time, type name known at compile time.
Type.GetType(): Runtime string lookup, type name as string. Slower, can fail.
// Compile-time
var t = typeof(Student); // Type known, compile-time safe
// Runtime
var t = Type.GetType("MyApp.Models.Student"); // String lookup, might be null
if (t == null) { } // Handle missing type
typeof() is preferred when type known. Type.GetType() for dynamic/plugin scenarios.
Use BindingFlags to include private, internal, static members.
public class Student
{
private int _internalId; // Private field
public string Name { get; set; }
}
// Get public members (default)
var props = typeof(Student).GetProperties(); // Only Name
// Get private + public
var fields = typeof(Student).GetFields(
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance
); // Includes _internalId
var field = typeof(Student)
.GetField("_internalId", BindingFlags.NonPublic | BindingFlags.Instance);
var value = field?.GetValue(student);
Use judiciously - accessing private members breaks encapsulation. Only when framework needs it.
Use ChatGPT, Claude, or Copilot to go deeper on C# reflection and attributes. Try these prompts:
"What is reflection in C# and when is it useful in real applications?""How do frameworks like ASP.NET Core and EF Core use attributes internally?""What are the performance implications of using reflection in C#?""Show me a real example where custom attributes save code duplication"
💡 Tip: After reading this article, paste your own code into AI and ask "What could go wrong here and why?" — fastest way to find edge cases and deepen understanding.