Reflection and Runtime Metadata
Learning Objectives
Distinguish reflection from RTTI.
Use reflection and annotations/attributes in C#, Python and Java; know TypeScript's runtime limits.
Apply reflection for plugins, serializers and DI — and understand the security costs.
Prerequisites: Modules 17, 22.
Intuition and Definition
Reflection is a program inspecting (and sometimes modifying) its own structure at run time: listing a type's fields and methods, reading attributes/annotations, creating instances by name, invoking methods dynamically.
RTTI only (typeid, dynamic_cast); compile-time type traits; static reflection is being standardised (C++26)
Full reflection (Class, Field, Method), annotations, dynamic proxies
Full reflection (Type, PropertyInfo), attributes, source generators as a compile-time alternative
Everything is inspectable: type(), vars(), getattr, inspect, descriptors, decorators
Types are erased; at run time only JavaScript objects remain (Object.keys, decorators with metadata in some setups)
Why It Matters
Frameworks run on reflection: serializers map fields to JSON, DI containers inspect constructors, test frameworks find [Test] methods, ORMs map properties to columns.
Syntax and Language Notes
// Java (not run)
for (Field f : Vehicle.class.getDeclaredFields()) System.out.println(f.getName() + ": " + f.getType());Complete Example
C# reflection with custom attributes — a mini validator driven by metadata:
Python introspection and descriptors — attributes are objects you can inspect and intercept:
using System.Reflection;
var v = new ServiceRecord { Vin = "WVWZZZ1KZ6W12345", Mileage = -10, Notes = "brake pads" };
foreach (var p in typeof(ServiceRecord).GetProperties())
{
var value = p.GetValue(v);
foreach (var rule in p.GetCustomAttributes<RuleAttribute>())
if (!rule.IsValid(value)) Console.WriteLine($"{p.Name}: {rule.Message} (got '{value}')");
}
var t = Type.GetType("PrintAction"); // create by name, as plugin hosts do
var action = (IAction)Activator.CreateInstance(t!)!;
action.Run();
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
public abstract class RuleAttribute : Attribute { public abstract string Message { get; } public abstract bool IsValid(object? v); }
public sealed class LengthAttribute(int n) : RuleAttribute
{
public override string Message => $"must have {n} characters";
public override bool IsValid(object? v) => v is string s && s.Length == n;
}
public sealed class NonNegativeAttribute : RuleAttribute
{
public override string Message => "must not be negative";
public override bool IsValid(object? v) => v is int i && i >= 0;
}
public sealed class ServiceRecord
{
[Length(17)] public st
…Vin: must have 17 characters (got 'WVWZZZ1KZ6W12345')
Mileage: must not be negative (got '-10')
PrintAction created via reflectionimport inspect
class Positive: # a descriptor: validation on attribute access
def __set_name__(self, owner, name): self.name = "_" + name
def __get__(self, obj, objtype=None): return getattr(obj, self.name)
def __set__(self, obj, value):
if value < 0: raise ValueError(f"{self.name[1:]} must be >= 0")
setattr(obj, self.name, value)
class Vehicle:
mileage = Positive()
def __init__(self, vin, mileage):
self.vin, self.mileage = vin, mileage
def service(self, km: int) -> str:
return f"service at {km}"
v = Vehicle("V1", 100)
print([name for name, _ in inspect.getmembers(Vehicle, inspect.isfunction)])
print(inspect.signature(Vehicle.service))
print(vars(v))
print(getattr(v, "service")(15000))
try:
v.mileage = -5
except ValueError as e:
print("ValueError:", e)['__init__', 'service']
(self, km: int) -> str
{'vin': 'V1', '_mileage': 100}
service at 15000
ValueError: mileage must be >= 0Step-by-Step Walkthrough
The C# validator enumerates properties, reads their custom attributes and calls each rule — the same mechanism behind System.ComponentModel.DataAnnotations.
Type.GetType("PrintAction") + Activator.CreateInstance creates an object from a string — how plugin hosts load implementations.
Python's inspect lists methods and signatures; vars shows instance state; getattr invokes by name.
The Positive descriptor intercepts attribute assignment — the machinery behind @property, ORMs and dataclass fields.
Common Mistakes
Reflection in hot paths — far slower than direct calls; cache metadata or generate code (C# source generators).
Breaking encapsulation with reflection on private members — brittle and a security risk.
Trimming/AOT problems: .NET trimming and Native AOT remove members that are only reached by reflection unless annotated.
Creating types from untrusted strings — arbitrary code execution; use an allow-list.
TypeScript: expecting interface types at run time.
Trade-offs and Alternatives
Flexible, generic frameworks Slow, unsafe, unfriendly to AOT/trimming
Compile-time generation (C# source generators, Java annotation processors, C++ templates/macros) Fast, type-safe Simple, auditable
Real-World Use
JSON serializers (Jackson, System.Text.Json), DI containers, ORMs (Hibernate, Entity Framework, SQLAlchemy), test discovery (JUnit, xUnit, pytest), plugin systems, Python dataclasses and attrs.
Practice
Q1. (Easy) Does C++ have full run-time reflection today?
Answer: No — only RTTI (typeid, dynamic_cast); static reflection is being added in C++26.
Q2. (Medium) Why are TypeScript interfaces unavailable at run time?
Answer: TypeScript erases all types when compiling to JavaScript.
Q3. (Advanced) Name two security risks of reflection.
Answer: Instantiating or invoking arbitrary types from untrusted input (code execution), and bypassing access control to read or modify private state (secrets, invariants). Mitigate with allow-lists, avoiding reflection on untrusted names, and running plugins with least privilege.
Interview Question
Q1. "What's the difference between RTTI and reflection?"
Answer: RTTI answers "what is the dynamic type of this object?" (identity checks, safe downcasts). Reflection goes further: enumerating a type's members, reading metadata (attributes/annotations), creating instances and invoking members by name at run time. Java, C# and Python provide reflection; C++ provides RTTI plus compile-time introspection via templates/type traits.
Quick Quiz
Q1. What is a Python descriptor?
Answer: An object with __get__/__set__/__delete__ that controls attribute access on another class.
Q2. Java's equivalent of C# attributes?
Answer: Annotations.
Q3. Why do .NET Native AOT apps dislike reflection?
Answer: Members reached only via reflection may be trimmed away, and dynamic code generation is limited.
Summary
Reflection inspects and manipulates program structure at run time; RTTI is a small subset.
Powers frameworks but costs speed, safety and AOT-friendliness; prefer compile-time generation where possible.
Related: Modules 22, 58, 62, 68.