Reflection, Attributes and Dynamic Programming
Reflection (System.Reflection) inspects and uses types at run time through their metadata:
Concept
Reflection (System.Reflection) inspects and uses types at run time through their metadata:
typeof(T), obj.GetType() A Type object type.GetProperties(), GetMethods(), GetFields(), GetConstructors()
PropertyInfo, MethodInfo, FieldInfo, ConstructorInfo
Filters: Public, NonPublic, Instance, Static, DeclaredOnly Activator.CreateInstance(type, args)
A new object of a type known only at run time prop.GetValue/SetValue, method.Invoke Dynamic member access
Assembly.GetExecutingAssembly().GetTypes(), Assembly.LoadFrom(path) Types of an assembly — plugin discovery
Reflection powers serializers, DI containers, test frameworks, ORMs and MVC model binding. Its costs: it is slower than direct calls, it bypasses compile-time checks (a renamed property becomes a run-time error), it can read private state (breaking encapsulation), and it conflicts with trimming and Native AOT (Module 28). Security: never load assemblies or invoke members named by untrusted input.
Attributes attach metadata to code: [Obsolete], [Serializable], [Flags], [Required], [Range], [JsonPropertyName], [Fact]. They do nothing by themselves — the compiler or code using reflection reads them.
A custom attribute is a class deriving from Attribute, restricted with [AttributeUsage(AttributeTargets.Property | ..., AllowMultiple = ...)]. Constructor parameters become positional arguments; public settable properties become named arguments. Attribute arguments must be compile-time constants, typeof expressions or arrays of these. Read them with GetCustomAttribute<T>().
dynamic defers member lookup to run time via the runtime binder (the DLR). It is convenient for COM automation (Excel, test-equipment drivers exposing COM), dynamic languages and some JSON scenarios, but errors surface as RuntimeBinderException and every call pays binding costs. Prefer interfaces and generics; use reflection when you need metadata; use dynamic only at interop boundaries.
Why It Matters
Plugin architectures (load diagnostic modules at run time), attribute-driven signal definitions, validation, serializers and test frameworks all rely on reflection and attributes. Knowing their cost and risks tells you when to avoid them — or when a source generator (Module 39) is the better tool.
Syntax
Type t = typeof(Vehicle);
PropertyInfo p = t.GetProperty("Year")!; object? v = p.GetValue(obj);
object o = Activator.CreateInstance(t, "Aurora", 2025)!;
[AttributeUsage(AttributeTargets.Property)] class SignalAttribute(uint id) : Attribute { }
var a = p.GetCustomAttribute<SignalAttribute>();
dynamic d = obj; Console.WriteLine(d.Model);Example
37.1 Inspect types, invoke members, attributes, validation, plugins, dynamic
Note the #:property PublishAot=false directive: file-based apps default to Native AOT settings, where unrestricted reflection triggers trimming warnings.
#:property PublishAot=false
using System.ComponentModel.DataAnnotations;
using System.Reflection;
// ---- Inspect a type ----
Type type = typeof(Vehicle);
Console.WriteLine($"{type.FullName}: class {type.IsClass}, base {type.BaseType!.Name}, interfaces {string.Join(",", type.GetInterfaces().Select(i => i.Name))}");
Console.WriteLine($"public methods declared here: {string.Join(", ", type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly).Where(m => !m.IsSpecialName).Select(m => m.Name))}");
foreach (PropertyInfo p in type.GetProperties())
Console.WriteLine($" property {p.Name,-8} : {p.PropertyType.Name,-7} writable {p.CanWrite}");
FieldInfo secret = type.GetField("_odometerKm", BindingFlags.NonPublic | BindingFlags.Instance)!;
// ---- Create objects and invoke members dynamically ----
object car = Activator.CreateInstance(type, "Aurora", 2025)!;
type.GetProperty(nameof(Vehicle.Color))!.SetValue(car, "Blue");
MethodInfo drive = type.GetMethod(nameof(Vehicle.Drive))!;
drive.Invoke(car, [120]);
Console.WriteLine($"dynamic call: {car}, private field read via reflection: {secret.GetValue(car)} km");
// ---- Attributes: built-in and custom, read w
…Expected Result
Reflection read the *private* _odometerKm field, created a Vehicle from a Type, discovered both plugins without any registration code, and Validator enforced [Range] and [MinLength] purely from attributes.
Vehicle: class True, base Object, interfaces IComparable`1
public methods declared here: Drive, Honk, CompareTo, ToString
property Model : String writable False
property Year : Int32 writable False
property Color : String writable True
property SpeedKmh : Double writable True
dynamic call: Blue Aurora 2025, private field read via reflection: 120 km
Year: Range 1990..2100
SpeedKmh: CAN 0x0CF bit 8 len 16
[Obsolete] on Honk: Use SoundHorn(duration) instead
validation ok: False; The field Model must be a string or array type with a minimum length of '2'.; The field Year must be between 1990 and 2100.
plugin DtcReader: 2 DTCs
plugin VinReader: EHBAUREV250000001
dynamic property access: Aurora 2025
reflection GetValue slower than direct access: TrueCommon Mistakes
Using reflection in hot paths without caching PropertyInfo/MethodInfo (or compiling delegates, Module 38).
Hard-coding member names as strings — use nameof.
Forgetting BindingFlags.Instance together with NonPublic/Public (returns nothing).
Assuming attributes execute code by themselves.
dynamic typos compile and fail only at run time.
Loading plugins from user-writable folders without verification.
Best Practices
Cache reflection results; generate code (source generators) for performance-critical paths.
Seal custom attribute classes and restrict them with [AttributeUsage].
Prefer interfaces for plugins (IDiagnosticPlugin) and use reflection only to discover them.
Keep AOT/trimming in mind: annotate with [DynamicallyAccessedMembers] or avoid reflection.
Limit dynamic to COM/interop edges.
Interview Relevance
Medium–high: "what is reflection used for?", "how do attributes work?", "how do you write a custom attribute?", "reflection vs dynamic?", "why is reflection slow and what are the alternatives?".
Practice Questions
Print every public property name and value of any object (a tiny object dumper).
Create a [Unit("V")] attribute and print signal values with their units.
Load all classes implementing ITestStep and run them in order of a [Order(n)] attribute.
Measure 1 000 000 calls: direct, MethodInfo.Invoke, and a cached delegate from CreateDelegate.
Quick Quiz
Q1. (Multiple choice) Which call returns private instance fields? A) GetFields() B) GetFields(BindingFlags.NonPublic) C) GetFields(BindingFlags.NonPublic | BindingFlags.Instance) D) GetField("x") Answer: C.
Q2. (True/False) [Obsolete] changes the behaviour of the method at run time.
Answer: False. It only makes the compiler emit a warning (or an error with [Obsolete(msg, true)]) at call sites.
Q3. (Predict the output) Validator.TryValidateObject on new Vehicle("X", 1850).
Answer: False, with two messages: minimum length of Model and Year out of range (verified).
Q4. (Find the bug) dynamic d = car; Console.WriteLine(d.Modle);
Answer: It compiles but throws Microsoft.CSharp.RuntimeBinder.RuntimeBinderException at run time — the member name is misspelled (verified).
Coding Challenge
Write Dump(object o) that prints Name = value for every public instance property of any object, and use it on a record Ecu("VCU", 0x7E0).
#:property PublishAot=false
using System.Reflection;
Dump(new Ecu("VCU", 0x7E0));
Dump(new Uri("https://example.com/ecus?id=1"), maxProperties: 3);
static void Dump(object o, int maxProperties = int.MaxValue)
{
Console.WriteLine(o.GetType().Name);
foreach (PropertyInfo p in o.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance).Take(maxProperties))
Console.WriteLine($" {p.Name} = {p.GetValue(o)}");
}
record Ecu(string Code, int Address);