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

  1. What reflection is and what it's for
  2. Getting a Type: typeof and GetType()
  3. Inspecting properties and methods: GetProperties() and GetMethods()
  4. Reading and writing property values through reflection
  5. Creating instances dynamically with Activator.CreateInstance
  6. ShowProperties: a generic inspector for BiblioTech's domain
  7. The cost of reflection and real-world use cases

  1. 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

  1. Getting a 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 namespace
typeof(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 LibraryItemGetType() always reveals the object's concrete type in memory, regardless of the type of the variable referencing it.

  1. Inspecting properties and methods: 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.

  1. 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.

  1. Creating instances dynamically with Activator.CreateInstance

Beyond 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.

  1. ShowProperties: a generic inspector for BiblioTech's domain

Combining 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 Martinez

ShowProperties 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)"]

  1. 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.Title directly is faster, safer, and more readable than looking it up by reflection with a string.
  • Forgetting to check for null on GetProperty/GetMethod: if the requested name doesn't exist (say, because of a typo in the string), these methods return null instead of throwing an exception; calling .GetValue(...) on that null without checking it throws a NullReferenceException.
  • Bypassing encapsulation with BindingFlags.NonPublic without a real need: it's technically possible to read or write private members through reflection, but doing so routinely breaks the design guarantees (like Available's private 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

  1. Using typeof and GetType(), confirm that typeof(Magazine) and new Magazine("Muy Interesante", "Editorial Staff", 350).GetType() represent the same Type. Also print type.Name and type.BaseType?.Name for Magazine (hint: BaseType returns the Type of the base class, here LibraryItem).

  2. Write the ShowProperties(object obj) function from this lesson and use it to show the properties of a Book, a Magazine, and a Member, each a different instance.

  3. Write a function bool HasProperty(object obj, string propertyName) that returns true if obj's type has a public property with that exact name (using GetProperty and checking whether the result is different from null), and false otherwise. Test it with "Title" (should give true on a Book) and with "Price" (should give false).

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.

© Copyright 2026. All rights reserved