Skip to main content

    Lesson 19 • Advanced Track

    Reflection & Dynamic Type Inspection

    By the end of this lesson you'll be able to inspect any type's structure at runtime — its properties, methods, and fields — read and set values by name, invoke methods and build objects dynamically, and read custom attributes. This is the machinery behind frameworks like ASP.NET, Entity Framework, and JSON serializers.

    What You'll Learn

    • Get a Type with typeof(T) or obj.GetType() and read its metadata
    • Enumerate properties, methods, and fields — including private ones with BindingFlags
    • Read and set property values by name with GetValue and SetValue
    • Invoke methods by name with MethodInfo.Invoke and build objects with Activator.CreateInstance
    • Define custom attributes and read them via reflection for metadata-driven code
    • Know when reflection is appropriate — and its performance and trimming trade-offs

    💡 Real-World Analogy

    Reflection is an X-ray for your code. Normally you interact with an object through its public buttons and levers — you call person.Greet() because you wrote that line knowing it exists. Reflection lets a program take an X-ray of a type it has never seen before: it can see every property, every method, every field — even the private ones tucked inside — and then act on what it finds. That's how a JSON serializer turns any object into text without you writing code for each class, and how a test runner discovers every method marked [Test]. The program inspects a type's own structure at runtime and reacts to it.

    The Reflection Vocabulary

    Reflection lives in the System.Reflection namespace and revolves around a family of "info" objects. Each one describes one piece of a type. Once you have a Type, everything else hangs off it.

    ObjectDescribesHow you get it
    TypeA whole class/struct/interfacetypeof(T) / obj.GetType()
    PropertyInfoOne propertytype.GetProperty("X") / GetProperties()
    MethodInfoOne methodtype.GetMethod("X") / GetMethods()
    FieldInfoOne fieldtype.GetField("X") / GetFields()
    ConstructorInfoOne constructortype.GetConstructor(...)
    AttributeA metadata tagmember.GetCustomAttribute<T>()

    By default, GetProperties() and friends return only the public instance members. To reach private or static members you must pass BindingFlags (covered in section 1).

    1. Inspecting a Type

    Everything starts with a Type object — the runtime's description of a class. Use typeof(Person) when you know the type at compile time, or obj.GetType() when you only have an instance. From a Type you can call GetProperties(), GetMethods(), and GetFields() to list its members. By default those see only public members; to include private ones you pass BindingFlags — a set of switches like Public | NonPublic | Instance. Read this worked example, run it, then you'll write your own loop.

    Worked example: X-ray a Person type

    Read every comment, run it, and watch reflection list properties, methods, and even the private field.

    Try it Yourself »
    C#
    using System;
    using System.Reflection;
    
    class Person
    {
        public string Name { get; set; } = "";
        public int Age { get; set; }
        private string secret = "hidden";
    
        public void Greet() => Console.WriteLine($"Hi, I'm {Name}!");
        private void Think() => Console.WriteLine("Thinking...");
    
        public string GetInfo(string prefix) => $"{prefix}: {Name}, age {Age}";
    }
    
    class Program
    {
        static void Main()
        {
            // Every type has a Type object describing its shape.
            // type
    ...

    Your turn. The program below gets a Type and loops over its properties — you just need to fill in how to get the type and how to print each property's name. Fill in the three ___ blanks, then run it.

    🎯 Your turn: list a type's properties

    Get the Type, loop GetProperties(), and print each property's type and name.

    Try it Yourself »
    C#
    using System;
    using System.Reflection;
    
    class Book
    {
        public string Title { get; set; } = "";
        public string Author { get; set; } = "";
        public int Pages { get; set; }
    }
    
    class Program
    {
        static void Main()
        {
            // 🎯 YOUR TURN — fill in the blanks marked with ___
    
            // 1) Get the Type object for the Book class.
            Type type = ___;            // 👉 use typeof(Book)
    
            Console.WriteLine($"Properties of {type.Name}:");
    
            // 2) Loop over every public pro
    ...

    2. Reading Values, Invoking Methods & Creating Objects

    Inspecting structure is half the story — reflection also lets you act on an instance by name. PropertyInfo.GetValue(obj) reads a property's value and SetValue(obj, value) writes it. MethodInfo.Invoke(obj, args) calls a method, with the arguments packed into an object[] (use null for none). And Activator.CreateInstance(type) builds an object without writing new — the trick that powers plugin systems that discover types at runtime. Read this worked example, then you'll wire up your own.

    Worked example: GetValue, SetValue, Invoke & CreateInstance

    Create an object dynamically, read and set a property, then call a method by name.

    Try it Yourself »
    C#
    using System;
    using System.Reflection;
    
    class Calculator
    {
        public string Owner { get; set; } = "Lab";
        public int Add(int a, int b) => a + b;
        public string Describe() => "I'm a Calculator!";
    }
    
    class Program
    {
        static void Main()
        {
            Type calcType = typeof(Calculator);
    
            // 1) Create an instance WITHOUT writing 'new Calculator()'.
            //    Activator.CreateInstance calls the parameterless constructor.
            object? calc = Activator.CreateInstance(calcType);  
    ...

    Now you try. Read a property value off an instance, find a method by name, and invoke it with one argument. Fill in the three ___ blanks:

    🎯 Your turn: read a value and invoke a method

    Use GetValue to read Language, then find and Invoke SayHello with an argument.

    Try it Yourself »
    C#
    using System;
    using System.Reflection;
    
    class Greeter
    {
        public string Language { get; set; } = "English";
        public string SayHello(string name) => $"Hello, {name}!";
    }
    
    class Program
    {
        static void Main()
        {
            // 🎯 YOUR TURN — fill in the blanks marked with ___
    
            var greeter = new Greeter();
            Type type = greeter.GetType();    // GetType() works on an existing object
    
            // 1) Read the Language property's VALUE off the greeter instance.
            PropertyInfo? 
    ...

    🔎 Deep Dive: BindingFlags — the visibility switches

    If GetMethod("Think") returns null for a method you know exists, the culprit is almost always visibility. The no-argument overloads return only public instance members. To reach anything else you must combine BindingFlags with a bitwise OR (|):

    var flags = BindingFlags.Public      // public members
              | BindingFlags.NonPublic   // private & protected members
              | BindingFlags.Instance;   // members that belong to an OBJECT
    
    // Now private methods show up:
    MethodInfo? think = type.GetMethod("Think", flags);
    
    // For static members, add Static (and drop Instance if you only want static):
    BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static

    Rule of thumb: you almost always need at least one of Instance or Static, plus at least one of Public or NonPublic. Forget the visibility pair and you'll silently get nothing back.

    3. Custom Attributes

    Attributes are metadata tags you attach to code in [Square brackets]. You define your own by extending Attribute, apply it to a class, method, or property, then read it back at runtime with GetCustomAttribute<T>(). This is the engine behind so much of .NET: ASP.NET discovers routes from [HttpGet], EF Core maps columns from [Required], and serializers skip fields marked [JsonIgnore]. The framework uses reflection to find your tags and act on them.

    Worked example: discover routes from attributes

    Define a [Route] attribute, tag some methods, and use reflection to discover them — a mini web framework.

    Try it Yourself »
    C#
    using System;
    using System.Reflection;
    
    // Define a custom attribute by extending Attribute.
    [AttributeUsage(AttributeTargets.Method)]
    class RouteAttribute : Attribute
    {
        public string Path { get; }
        public string Verb { get; }
        public RouteAttribute(string path, string verb = "GET")
        {
            Path = path;
            Verb = verb;
        }
    }
    
    // Apply attributes — they're metadata tags attached to code.
    class UserController
    {
        [Route("/api/users", "GET")]
        public void List() => Conso
    ...

    Pro Tips

    • 💡 Cache reflection results: GetProperties() and GetMethod() are expensive. If you call them repeatedly, store the PropertyInfo/MethodInfo in a static field or dictionary and reuse it.
    • 💡 Prefer nameof over string literals: GetMethod(nameof(Calculator.Add)) updates automatically when you rename the method; GetMethod("Add") silently breaks.
    • 💡 Use source generators for hot paths: in .NET 6+, source generators (like System.Text.Json's) produce compile-time code that's far faster than runtime reflection and survives trimming.
    • 💡 Convert MethodInfo to a delegate with CreateDelegate if you'll call it many times — repeated Invoke is slow because of boxing and argument arrays.

    Common Errors (and the fix)

    • Reflection in a hot path is slow: a reflected call is roughly 10–100× slower than a direct one because of boxing, security checks, and member lookups. Never put Invoke or GetProperties() inside a tight loop — cache the lookup outside it, or avoid reflection entirely there.
    • GetMethod("Think") returns null: the member is private or static, so the default search misses it. Pass BindingFlags.NonPublic | BindingFlags.Instance (add Static for static members).
    • "System.Reflection.TargetInvocationException": the method you invoked threw its own exception. Invoke wraps it, so read ex.InnerException to see the real error. Also remember GetValue/Invoke box value types into object.
    • "System.Reflection.TargetException: Non-static method requires a target": you passed null as the instance for an instance member. Pass the object: prop.GetValue(obj), not prop.GetValue(null). null is only valid for static members.
    • Trimming/AOT removes your types: the publish trimmer and Native AOT drop members they can't see being used. Code only reached via reflection looks "unused" and gets cut, causing null lookups at runtime. Annotate with [DynamicallyAccessedMembers] or prefer source generators for trimmed/AOT apps.

    📋 Quick Reference

    TaskCodeNotes
    Get type (compile time)typeof(Person)When you know the type
    Get type (from instance)obj.GetType()At runtime
    List propertiestype.GetProperties()Public instance by default
    One method by nametype.GetMethod("Add")null if not found
    See private members..., BindingFlags.NonPublic | InstancePass the flags
    Read a valueprop.GetValue(obj)Returns object (boxed)
    Write a valueprop.SetValue(obj, val)Sets the property
    Call a methodmethod.Invoke(obj, new object[]{ 5, 3 })null args for none
    Create an objectActivator.CreateInstance(type)No new needed
    Read an attributemember.GetCustomAttribute<T>()null if absent

    Frequently Asked Questions

    Q: What's the difference between typeof and GetType()?

    typeof(Person) works at compile time when you already know the type. obj.GetType() works at runtime on an instance and returns the object's actual type — which may be a subclass. If Animal a = new Dog();, then typeof(Animal) is Animal but a.GetType() is Dog.

    Q: Why is my private method or field invisible to reflection?

    The default GetMethod/GetField only returns public members. Add BindingFlags.NonPublic | BindingFlags.Instance (plus Static for static members) to the call to reach private ones.

    Q: Is reflection slow enough to worry about?

    For occasional use — startup, configuration, a one-off scan — it's fine. In a tight loop it's a problem: it can be 10–100× slower than a direct call. Cache the PropertyInfo/MethodInfo outside the loop, or switch to source generators or compiled delegates for hot paths.

    Q: Why did Invoke throw a TargetInvocationException instead of the real error?

    When the method you invoked throws, reflection wraps that exception in a TargetInvocationException. The original error is in its InnerException — inspect that to see what actually went wrong.

    Mini-Challenge: A Generic Object Dumper

    No blanks this time — just a brief and an outline. Write a Dump(object obj) method that uses reflection to print every public property of any object as Name = Value — the same trick a debugger or serializer uses. Run it on the Product and check your output against the expected lines in the comments.

    🎯 Mini-Challenge: build an object dumper

    Loop the type's properties and print each name and value via reflection.

    Try it Yourself »
    C#
    using System;
    using System.Reflection;
    
    // A small class to dump. Feel free to add more properties.
    class Product
    {
        public string Name { get; set; } = "Widget";
        public decimal Price { get; set; } = 9.99m;
        public bool InStock { get; set; } = true;
    }
    
    class Program
    {
        // 🎯 MINI-CHALLENGE: write a generic "object dumper".
        // Dump(object obj) should print every PUBLIC property as  Name = Value.
        // 1. Get the object's Type with obj.GetType().
        // 2. Loop over type.GetProper
    ...

    🎉 Lesson Complete

    • typeof(T) and obj.GetType() give you the Type metadata object
    • GetProperties(), GetMethods(), GetFields() enumerate a type's members
    • BindingFlags (Public | NonPublic | Instance | Static) reach private and static members
    • GetValue/SetValue read and write properties; MethodInfo.Invoke calls methods by name
    • Activator.CreateInstance builds objects without new
    • ✅ Custom attributes add metadata; read them with GetCustomAttribute<T>()
    • ✅ Reflection is powerful but slow and trimming-sensitive — cache lookups and prefer source generators in hot paths
    • Next lesson: Generics: Advanced — constraints, variance, and reusable type-safe code

    Sign up for free to track which lessons you've completed and get learning reminders.

    Previous

    Cookie & Privacy Settings

    We use cookies to improve your experience, analyze traffic, and show personalized ads. You can manage your preferences below.

    By clicking "Accept All", you consent to our use of cookies for analytics and personalized advertising. You can customize your preferences or reject non-essential cookies.

    Privacy PolicyTerms of Service