Inspect types, properties and methods at run time with System.Reflection, create objects and invoke members dynamically, understand the cost, and see where frameworks rely on it.

Bishal Biswas
Self Learner
Code that looks at code
Normally you know the types you work with when you write the program. Reflection lets a program examine types at run time: what properties does this object have, what methods, what attributes, and then read, write or call them by name.
It is how JSON serialisers turn any object into text, how test frameworks find your test methods, how dependency injection containers build objects, and how ORMs map classes to tables. You will not use it every day, but you will use libraries built on it every day.
Getting a Type
Everything starts with a System.Type object, the description of a type:
Type t1 = typeof(string); // from a type name at compile time
Type t2 = "hello".GetType(); // from an object at run time
Type? t3 = Type.GetType("System.Int32"); // from a string, null if not found
Console.WriteLine(t1.Name); // String
Console.WriteLine(t1.FullName); // System.String
Console.WriteLine(t1.Namespace); // System
Console.WriteLine(t1.IsClass); // True
Console.WriteLine(t2.BaseType?.Name); // Object
Listing members
class Person
{
public string Name { get; set; } = "";
public int Age { get; set; }
private string secret = "hidden";
public string Greet(string greeting) => $"{greeting}, {Name}";
}
Type type = typeof(Person);
foreach (var prop in type.GetProperties())
{
Console.WriteLine($"Property {prop.Name} : {prop.PropertyType.Name}");
}
// Property Name : String
// Property Age : Int32
foreach (var method in type.GetMethods())
{
Console.WriteLine($"Method {method.Name}");
}
// Greet, plus get_Name, set_Name, ToString, Equals and the rest inherited from object
By default only public members are returned. Pass binding flags to see more:
using System.Reflection;
var privateFields = type.GetFields(BindingFlags.NonPublic | BindingFlags.Instance);
foreach (var f in privateFields) Console.WriteLine(f.Name); // secret
Reading and writing properties by name
var person = new Person { Name = "Asha", Age = 30 };
PropertyInfo ageProp = typeof(Person).GetProperty("Age")!;
object? value = ageProp.GetValue(person);
Console.WriteLine(value); // 30
ageProp.SetValue(person, 31);
Console.WriteLine(person.Age); // 31
This is the engine inside every “map this object to a dictionary” or “copy matching properties” helper:
static Dictionary<string, object?> ToDictionary(object obj)
{
return obj.GetType()
.GetProperties()
.ToDictionary(p => p.Name, p => p.GetValue(obj));
}
foreach (var (key, val) in ToDictionary(person))
{
Console.WriteLine($"{key} = {val}");
}
Invoking methods
MethodInfo greet = typeof(Person).GetMethod("Greet")!;
object? result = greet.Invoke(person, new object[] { "Hello" });
Console.WriteLine(result); // Hello, Asha
Arguments go in an object[], and the return comes back as object. Static methods pass null as the instance.
Creating objects
Type type = typeof(Person);
object instance = Activator.CreateInstance(type)!;
// With constructor arguments
object list = Activator.CreateInstance(typeof(List<int>), 100)!; // capacity 100
// From a type name in a config file
Type? pluginType = Type.GetType("MyApp.Plugins.CsvExporter, MyApp");
var plugin = (IExporter)Activator.CreateInstance(pluginType!)!;
That last form is how plugin systems load implementations chosen at run time.
Generic types at run time
Type open = typeof(List<>); // generic definition, no argument
Type closed = open.MakeGenericType(typeof(string)); // List<string>
var list = Activator.CreateInstance(closed);
Console.WriteLine(typeof(List<int>).IsGenericType); // True
Console.WriteLine(typeof(List<int>).GetGenericArguments()[0]); // System.Int32
Assemblies
An assembly is a compiled .dll or .exe. You can scan every type in one:
Assembly current = Assembly.GetExecutingAssembly();
var testClasses = current.GetTypes()
.Where(t => t.Name.EndsWith("Tests") && t.IsClass);
foreach (var t in testClasses) Console.WriteLine(t.FullName);
Test runners do exactly this, then look for methods with a [Test] or [Fact] attribute. Attributes are the next topic and are the usual partner of reflection.
Reading attributes
[Obsolete("Use Person2")]
class Person { }
var attr = typeof(Person).GetCustomAttribute<ObsoleteAttribute>();
Console.WriteLine(attr?.Message); // Use Person2
The cost
Reflection is slow compared to direct code, often a hundred times slower per call, because every lookup is a string search and every invoke boxes its arguments. Mitigations:
- Cache
PropertyInfoandMethodInfoobjects instead of looking them up on every call. - Build a delegate once with
MethodInfo.CreateDelegateor expression trees, then call the delegate. - Use source generators for serialisers and mappers, which move the work to compile time. That topic comes later.
Reflection also breaks when code is trimmed for AOT compilation, because the trimmer cannot see what you will ask for by name. Modern libraries lean on source generators for this reason.
dynamic
The dynamic keyword is reflection with nicer syntax. Member lookup happens at run time:
dynamic d = new Person { Name = "Ravi" };
Console.WriteLine(d.Name); // works
Console.WriteLine(d.Missing); // RuntimeBinderException at run time
Useful for interop with COM or scripting languages. Avoid it elsewhere, since you lose every compile time check.
Quick recap
- Reflection inspects and manipulates types, members and attributes at run time.
typeofandGetTypegive you aType, andGetProperties,GetMethodsandGetFieldslist members.GetValue,SetValueandInvokeread, write and call members by name.Activator.CreateInstancebuilds objects from aType, including ones named in config.- It is slow and trimming unfriendly, so cache lookups or prefer source generators.
- Frameworks for serialisation, testing and dependency injection are built on it.
// Reflection in C#
using System.Reflection;
class Person
{
public string Name { get; set; } = "";
public int Age { get; set; }
private string secret = "hidden";
public string Greet(string greeting) => $"{greeting}, {Name}";
public static Person Default() => new() { Name = "Anonymous" };
}
var person = new Person { Name = "Asha", Age = 30 };
Type type = person.GetType();
// Inspect the type
Console.WriteLine($"{type.Name} in {type.Namespace ?? "global"}, base {type.BaseType?.Name}");
foreach (PropertyInfo p in type.GetProperties())
Console.WriteLine($" property {p.Name} : {p.PropertyType.Name}");
foreach (FieldInfo f in type.GetFields(BindingFlags.NonPublic | BindingFlags.Instance))
Console.WriteLine($" private field {f.Name} = {f.GetValue(person)}");
// Read and write a property by name
PropertyInfo age = type.GetProperty("Age")!;
age.SetValue(person, (int)age.GetValue(person)! + 1);
Console.WriteLine(person.Age); // 31
// Invoke methods by name, instance and static
MethodInfo greet = type.GetMethod("Greet")!;
Console.WriteLine(greet.Invoke(person, new object[] { "Hello" })); // Hello, Asha
MethodInfo factory = type.GetMethod("Default")!;
var made = (Person)factory.Invoke(null, null)!;
Console.WriteLine(made.Name); // Anonymous
// Create instances from a Type, including closed generics
var fresh = (Person)Activator.CreateInstance(type)!;
Type listType = typeof(List<>).MakeGenericType(typeof(string));
var list = (System.Collections.IList)Activator.CreateInstance(listType)!;
list.Add("built at run time");
Console.WriteLine($"{listType.Name} with {list.Count} item");
// A generic object to dictionary helper, the core of every serialiser
static Dictionary<string, object?> ToDictionary(object obj)
=> obj.GetType().GetProperties().ToDictionary(p => p.Name, p => p.GetValue(obj));
foreach (var (key, value) in ToDictionary(person))
Console.WriteLine($"{key} = {value}");
// Scan the current assembly for types
int typeCount = Assembly.GetExecutingAssembly().GetTypes().Count(t => t.IsClass);
Console.WriteLine($"Classes in this assembly: {typeCount}");
Was this explanation helpful?
Your feedback helps us improve the content for everyone.
Thank you for your feedback!
We appreciate you helping us improve.
What could we improve? (Optional)
Read aloud
Ready