Up to now, every piece of BiblioTech's code has known, at compile time, exactly which
properties and methods each class has: when you write book1.Title, the compiler checks that
Book has a Title property before it ever generates the program. Reflection
(System.Reflection) turns that idea on its head: it lets a program inspect and manipulate its
own types at runtime, without knowing them in advance. It's literally the mechanism that lets
JsonSerializer (Module 5) know what properties a Book has without anyone having written
mapping code specific to that class: underneath, it walks its properties by reflection. This
lesson opens Module 6 by explaining how that mechanism works and how to use it directly.
Contents
- What reflection is and what it's for
- Getting a
Type:typeofandGetType() - Inspecting properties and methods:
GetProperties()andGetMethods() - Reading and writing property values through reflection
- Creating instances dynamically with
Activator.CreateInstance ShowProperties: a generic inspector for BiblioTech's domain- The cost of reflection and real-world use cases
- What reflection is and what it's for
Reflection is a program's ability to examine its own structure — classes, properties, methods,
attributes — as if it were data, and to act on it without the code using it knowing in advance
the concrete type it's working with. The entire System.Reflection namespace revolves around one
central type: System.Type, which represents, at runtime, "the complete description of a class"
(its properties, its methods, its base class, its interfaces...).
Reflection isn't a tool you reach for daily in the domain code of an application like BiblioTech
(book1.Title is still written directly, with no reflection involved, in 99% of the code). Its
value shows up in generic, infrastructural code: frameworks that need to work with "any type"
without knowing it ahead of time. You've already used several pieces built exactly this way
without realizing it:
| Tool already seen in the course | What it does with reflection underneath |
|---|---|
JsonSerializer.Serialize/Deserialize (Module 5) |
Walks a type's public properties to read or assign them |
Entity Framework Core (DbSet<T>) |
Inspects Book's, Member's... properties to map them to columns |
| Dependency injection frameworks (Module 8) | Create instances of classes whose concrete type they don't know at compile time |
- Getting a
Type: typeof and GetType()
Type: typeof and GetType()There are two ways to get the Type that represents a class, depending on whether the type is
known at compile time or you only have an instance at runtime:
using System;
// Way 1: typeof, when the type is known at compile time
Type bookType = typeof(Book);
// Way 2: GetType(), when you only have an instance (the type might not be known in advance)
Book book1 = new Book("Hopscotch", "Julio Cortazar", "978-84-376-0495-4");
Type typeFromInstance = book1.GetType();
Console.WriteLine(bookType == typeFromInstance); // True: both represent the same Type
Console.WriteLine(bookType.Name); // "Book"
Console.WriteLine(bookType.FullName); // the full name, including namespacetypeof(T) |
object.GetType() |
|
|---|---|---|
| When it's used | The type is known in code, written literally | Only a runtime reference is available |
| Result under polymorphism | The exact type named between the parentheses | The object's real type, even if the variable is of a more general type |
The second nuance matters: if LibraryItem item = book1; (recalling Polymorphism from Module 3),
item.GetType() returns Book, not LibraryItem — GetType() always reveals the object's
concrete type in memory, regardless of the type of the variable referencing it.
- Inspecting properties and methods:
GetProperties() and GetMethods()
GetProperties() and GetMethods()A Type exposes, among many others, the methods GetProperties() and GetMethods(), which
return arrays of PropertyInfo and MethodInfo respectively: objects that describe, one by one,
each public property or method of the type.
using System.Reflection;
Type bookType = typeof(Book);
PropertyInfo[] properties = bookType.GetProperties();
foreach (PropertyInfo property in properties)
{
Console.WriteLine($"Property: {property.Name} (type {property.PropertyType.Name})");
}
// Property: Title (type String)
// Property: Author (type String)
// Property: Available (type Boolean)
// Property: Isbn (type String)
MethodInfo[] methods = bookType.GetMethods();
foreach (MethodInfo method in methods)
{
Console.WriteLine($"Method: {method.Name}");
}
// Method: Lend, Return, ShowDetails, Describe, Matches, GetType, ToString... (inherited members included)GetProperties() on typeof(Book) includes both the properties declared directly on Book
(Isbn) and those inherited from LibraryItem (Title, Author, Available): reflection
respects the inheritance hierarchy exactly as the language itself does. GetMethods(), by
default, also includes methods inherited from object (ToString(), GetType(), Equals()...),
which is worth keeping in mind when iterating over the result.
- Reading and writing property values through reflection
A PropertyInfo isn't just a description: it also lets you read (GetValue) or write
(SetValue) that property's value on a given instance, passed in as an argument:
Book book1 = new Book("Hopscotch", "Julio Cortazar", "978-84-376-0495-4");
PropertyInfo? titleProperty = typeof(Book).GetProperty("Title");
object? titleValue = titleProperty?.GetValue(book1);
Console.WriteLine(titleValue); // "Hopscotch"
titleProperty?.SetValue(book1, "Hopscotch (revised edition)");
Console.WriteLine(book1.Title); // "Hopscotch (revised edition)"GetProperty("Title") looks up, by its name as a string, the specific Title property; it
returns null if none exists with that exact name, which is why GetValue is called with ?.
This is exactly what sets reflection apart from accessing book1.Title directly: the property
name can be data (a string coming from configuration, from a file, or computed inside a
loop), not a fixed identifier written in the code.
Available, with its private set (Module 3), still keeps its encapsulation intact against
SetValue by default: GetProperty("Available")?.SetValue(book1, false) throws an exception,
unless non-public member access is explicitly requested with BindingFlags.NonPublic — a
possibility that exists but is best avoided, since bypassing a private set through reflection
breaks precisely the guarantee that private set was meant to provide.
- Creating instances dynamically with
Activator.CreateInstance
Activator.CreateInstanceBeyond inspecting already-instantiated objects, reflection lets you create new instances of a
type known only at runtime (for example, from its name as a string), with
Activator.CreateInstance:
using System;
Type memberType = typeof(Member);
// Activator.CreateInstance needs the exact arguments of one of Member's constructors
object? newMember = Activator.CreateInstance(memberType, 99, "Dynamically Created Member");
if (newMember is Member member)
{
Console.WriteLine($"{member.Id}: {member.Name}"); // 99: Dynamically Created Member
}Activator.CreateInstance(type, arguments...) looks, among type's public constructors, for one
whose signature matches the given arguments, and invokes it; the result comes back as object,
so it needs an is/as (Module 3) to be treated again as a Member with all its members. This
mechanism underpins many frameworks: a dependency injection container (mentioned in passing here;
picked up again in Module 8) receives, at runtime, a list of types to instantiate without knowing
them at compile time, and uses Activator.CreateInstance (or equivalent, more optimized
mechanisms) to build each one.
ShowProperties: a generic inspector for BiblioTech's domain
ShowProperties: a generic inspector for BiblioTech's domainCombining GetType(), GetProperties(), and GetValue(), you can write a single function that
prints the name and value of every public property of any object, without needing a separate
ShowDetails() method for every class in the domain:
using System.Reflection;
static void ShowProperties(object obj)
{
Type type = obj.GetType();
Console.WriteLine($"--- {type.Name} ---");
foreach (PropertyInfo property in type.GetProperties())
{
object? value = property.GetValue(obj);
Console.WriteLine($"{property.Name}: {value}");
}
}Book book1 = new Book("Hopscotch", "Julio Cortazar", "978-84-376-0495-4");
Member member1 = new Member(1, "Ana Martinez");
ShowProperties(book1);
// --- Book ---
// Title: Hopscotch
// Author: Julio Cortazar
// Available: True
// Isbn: 978-84-376-0495-4
ShowProperties(member1);
// --- Member ---
// Id: 1
// Name: Ana MartinezShowProperties takes object obj (the most general type possible, Module 3) precisely because
it doesn't need to know in advance whether it will receive a Book, a Magazine, or a Member:
it uses obj.GetType() to discover that at runtime, and from there walks its properties
generically. It's a very practical debugging tool: a single method serves to inspect the internal
state of any domain object, with no need to write or maintain a custom ShowDetails() for every
new class added to BiblioTech.
flowchart LR
A["ShowProperties(obj)"] --> B["obj.GetType()"]
B --> C["type.GetProperties()"]
C --> D["For each PropertyInfo: property.GetValue(obj)"]
D --> E["Console.WriteLine(name + value)"]
- The cost of reflection and real-world use cases
Reflection has a noticeable performance cost compared to direct access (book1.Title): locating a
member by its name, checking types and signatures at runtime, is noticeably slower than a call
resolved at compile time. That doesn't mean it should always be avoided, just that it should be
reserved for where it truly adds value:
| Use case | Why it fits reflection |
|---|---|
Generic serializers (JsonSerializer) |
Need to work with any class, with no type-specific code |
| Dependency injection frameworks (Module 8) | Create instances of externally configured types, unknown at compile time |
Debugging/inspection tools (ShowProperties) |
The benefit of generic inspection outweighs the cost, because it doesn't run on the performance-critical path |
Everyday domain code (book1.Lend()) |
Doesn't fit: direct access is faster, safer at compile time, and more readable |
Common Mistakes and Tips
- Using reflection where direct access already works: if the type is known at compile time
(the usual case in BiblioTech's domain code), accessing
book1.Titledirectly is faster, safer, and more readable than looking it up by reflection with a string. - Forgetting to check for
nullonGetProperty/GetMethod: if the requested name doesn't exist (say, because of a typo in the string), these methods returnnullinstead of throwing an exception; calling.GetValue(...)on thatnullwithout checking it throws aNullReferenceException. - Bypassing encapsulation with
BindingFlags.NonPublicwithout a real need: it's technically possible to read or writeprivatemembers through reflection, but doing so routinely breaks the design guarantees (likeAvailable'sprivate set) that the code itself deliberately established. - Not measuring the performance impact before using reflection on a critical path: in a loop that runs millions of times, the difference between direct access and reflection can be significant; for infrastructure code that's very performance-sensitive, more advanced alternatives exist (expression compilation, Source Generators) beyond the scope of this course.
- Tip: before writing code with reflection, ask yourself whether the problem can be solved with tools you've already seen (interfaces, generics, polymorphism); reflection is the right tool when the concrete type isn't known until runtime, not a general substitute for good object-oriented design.
Exercises
-
Using
typeofandGetType(), confirm thattypeof(Magazine)andnew Magazine("Muy Interesante", "Editorial Staff", 350).GetType()represent the sameType. Also printtype.Nameandtype.BaseType?.NameforMagazine(hint:BaseTypereturns theTypeof the base class, hereLibraryItem). -
Write the
ShowProperties(object obj)function from this lesson and use it to show the properties of aBook, aMagazine, and aMember, each a different instance. -
Write a function
bool HasProperty(object obj, string propertyName)that returnstrueifobj's type has a public property with that exact name (usingGetPropertyand checking whether the result is different fromnull), andfalseotherwise. Test it with"Title"(should givetrueon aBook) and with"Price"(should givefalse).
Solutions
Type typeByTypeof = typeof(Magazine);
Magazine magazine1 = new Magazine("Muy Interesante", "Editorial Staff", 350);
Type typeByInstance = magazine1.GetType();
Console.WriteLine(typeByTypeof == typeByInstance); // True
Console.WriteLine(typeByTypeof.Name); // "Magazine"
Console.WriteLine(typeByTypeof.BaseType?.Name); // "LibraryItem"
static void ShowProperties(object obj)
{
Type type = obj.GetType();
Console.WriteLine($"--- {type.Name} ---");
foreach (PropertyInfo property in type.GetProperties())
{
Console.WriteLine($"{property.Name}: {property.GetValue(obj)}");
}
}
Book book1 = new Book("Hopscotch", "Julio Cortazar", "978-84-376-0495-4");
Magazine magazine1 = new Magazine("Muy Interesante", "Editorial Staff", 350);
Member member1 = new Member(1, "Ana Martinez");
ShowProperties(book1);
ShowProperties(magazine1);
ShowProperties(member1);
static bool HasProperty(object obj, string propertyName)
{
return obj.GetType().GetProperty(propertyName) != null;
}
Book book1 = new Book("Hopscotch", "Julio Cortazar", "978-84-376-0495-4");
Console.WriteLine(HasProperty(book1, "Title")); // True
Console.WriteLine(HasProperty(book1, "Price")); // False
Conclusion
In this lesson you've met reflection: how to get a class's Type with typeof or GetType(),
how to inspect its properties and methods with GetProperties()/GetMethods(), how to read and
write values through reflection with GetValue()/SetValue(), and how to create instances
dynamically with Activator.CreateInstance. You've also seen that reflection is precisely the
mechanism that makes tools you've already used possible, like JsonSerializer or Entity Framework
Core, and why it's best reserved for generic infrastructure code rather than everyday domain work.
The next lesson, Attributes, picks up something you've already seen in passing without stopping
to examine it: [JsonPropertyName] and [JsonPolymorphic] from the Serialization lesson are, in
fact, attributes — a way of annotating code with additional metadata. And the reflection
you've just learned is exactly the tool that lets you read those attributes at runtime to make
decisions — the missing piece for understanding how they really work underneath.
C# Programming Course
Module 1: Introduction to C#
- Introduction to C#
- Setting Up the Development Environment
- Hello World Program
- Basic Syntax and Structure
- Variables and Data Types
- Arrays and Strings
Module 2: Control Structures
Module 3: Object-Oriented Programming
- Classes and Objects
- Methods
- Constructors and Destructors
- Inheritance
- Polymorphism
- Encapsulation
- Abstraction
- Structs and Records: Value Types and Reference Types
Module 4: Advanced C# Concepts
- Interfaces
- Delegates and Events
- Pattern Matching and Modern C# Features
- Generics
- Collections
- LINQ (Language Integrated Query)
- Asynchronous Programming
Module 5: Working with Data
- File I/O
- Serialization
- Database Connectivity
- Entity Framework
- Working with JSON and Consuming REST APIs
Module 6: Advanced Topics
- Reflection
- Attributes
- Dynamic Programming
- Memory Management and Garbage Collection
- Multithreading and Parallel Programming
Module 7: Building Applications
Module 8: Best Practices and Design Patterns
- Coding Standards and Best Practices
- Design Patterns
- Dependency Injection and Inversion of Control
- Unit Testing
- Code Review and Refactoring
