C# Reflection: Get Properties at Runtime
c# reflection get properties: Learn how to use Type.GetProperties() to inspect and manipulate properties at runtime, filter with BindingFlags, and avoid common reflect...
When you need to inspect an object's structure at runtime, Type.GetProperties() is the entry point. The method returns an array of PropertyInfo objects that describe each property on a type, including its name, type, and accessibility. The c# reflection get properties pattern is built on this call: discover what a type exposes, then read or write values through the returned metadata.
The Basic Call
Call GetProperties() on any Type instance to retrieve the public properties declared on that type and its base classes.
using System; using System.Reflection; public class Customer { public int Id { get; set; } public string Name { get; set; } public string Email { get; set; } } class Program { static void Main() { Type type = typeof(Customer); PropertyInfo[] properties = type.GetProperties(); foreach (PropertyInfo property in properties) { Console.WriteLine($"{property.Name} : {property.PropertyType}"); } } }
The output lists Id, Name, and Email with their corresponding types. Note that GetProperties() returns only public properties by default. Private, protected, and internal properties are excluded unless you pass explicit binding flags.
Understanding PropertyInfo
Each PropertyInfo instance gives you more than just a name. The key members are:
Name— the property identifierPropertyType— the type of the value the property holdsCanReadandCanWrite— whether the property has a getter or setterGetGetMethod()andGetSetMethod()— returnMethodInfofor the accessorsGetValue(object)— reads the current value from an instanceSetValue(object, value)— writes a value to an instance
A property with only a getter (such as a computed property) will have CanRead == true and CanWrite == false. Calling SetValue on such a property throws an ArgumentException at runtime, so always check CanWrite before attempting to assign.
Filtering with BindingFlags
The default overload returns public instance and static properties. When you need more control, pass BindingFlags:
PropertyInfo[] allProperties = type.GetProperties( BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
This combination returns every property on the type, including private ones. A few rules worth remembering:
BindingFlags.PublicandBindingFlags.NonPublicare independent filters. Combining both returns properties from both accessibility groups.BindingFlags.InstanceandBindingFlags.Staticare also independent filters.- Static properties declared on base classes are not returned unless you add
BindingFlags.FlattenHierarchy. Instance properties from base classes are returned automatically.
A common mistake is passing only BindingFlags.NonPublic without Instance or Static. The result is an empty array because the flags must explicitly specify which kind of member to search for.
Reading and Writing Property Values
Once you have a PropertyInfo, you can use it to read or modify values on a concrete instance:
var customer = new Customer { Id = 1, Name = "Ada" }; PropertyInfo nameProperty = typeof(Customer).GetProperty("Name"); string currentName = (string)nameProperty.GetValue(customer); nameProperty.SetValue(customer, "Grace");
GetValue boxes value types into object, so you need a cast when assigning the result. For value-type properties, SetValue accepts the boxed value and unboxes it internally. Passing the wrong type causes an ArgumentException.
Indexer properties are a special case. A property with index parameters, such as this[int index], cannot be read with a single-argument GetValue. You must supply the index values:
PropertyInfo indexer = typeof(List<string>).GetProperty("Item"); string first = (string)indexer.GetValue(list, new object[] { 0 });
The indexer's name is always Item in the metadata, regardless of how it is declared in source code.
Performance Considerations
Reflection is not free. GetProperties() performs metadata lookup, and each GetValue or SetValue call involves argument validation, boxing, and a virtual dispatch through the reflection layer. For code that runs once during startup—such as building a mapping table or validating a configuration object—the cost is irrelevant. For code that executes inside a hot loop, the same reflection calls can dominate the profile.
Two practical mitigations:
- Cache the
PropertyInfoarray. Do not callGetProperties()on every iteration of a loop. - Cache delegates.
PropertyInfohasGetGetMethod()andGetSetMethod(); you can convert thoseMethodInfoinstances into strongly typed delegates usingDelegate.CreateDelegateor expression trees. This removes the per-call reflection overhead while keeping the discovery dynamic.
var getter = (Func<Customer, string>)Delegate.CreateDelegate( typeof(Func<Customer, string>), nameProperty.GetGetMethod());
The delegate approach works well when the property type is known at the call site. If the property type is also dynamic, expression trees are a more flexible alternative, though they add complexity.
Common Pitfalls
Several behaviors routinely surprise developers working with property reflection.
Properties are not fields. GetProperties() ignores public fields. If a type exposes public string Name; as a field, it will not appear in the result. Use GetFields() for fields.
Inherited properties. GetProperties() without flags returns public properties from the full inheritance chain. For an interface, it returns the properties declared on that interface, not the implementing class's additional members.
Ambiguous matches. GetProperty(string name) throws an AmbiguousMatchException when a property name exists in both the type and a base class with different return types. This is rare but possible with new keyword hiding. Use GetProperty(name, BindingFlags.DeclaredOnly) to restrict the search to the current type.
Null instances. Calling GetValue(null) on an instance property throws a TargetException. Static properties accept null as the instance argument.
Practical Use Cases
The most common production use of property reflection is serialization and mapping. A generic object mapper reads source properties and writes matching target properties:
public static void CopyProperties(object source, object target) { PropertyInfo[] sourceProps = source.GetType().GetProperties(); PropertyInfo[] targetProps = target.GetType().GetProperties(); foreach (PropertyInfo sourceProp in sourceProps) { PropertyInfo targetProp = Array.Find( targetProps, p => p.Name == sourceProp.Name && p.PropertyType == sourceProp.PropertyType); if (targetProp != null && targetProp.CanWrite) { targetProp.SetValue(target, sourceProp.GetValue(source)); } } }
This pattern appears in DTO mapping, form binding, and configuration binding. It trades compile-time safety for flexibility: a renamed property silently produces no mapping, so validation of the mapping result is often necessary.
Another common use is building dynamic display tables, where column names come from PropertyInfo.Name and values from GetValue. UI frameworks and reporting tools use this approach to render arbitrary object graphs without per-type code.
When to Avoid Reflection
If the set of properties is known at compile time, direct property access is faster, type-safe, and easier to debug. Reflection should be reserved for cases where the type is genuinely unknown until runtime: plugin systems, generic serializers, dynamic form builders, and metadata-driven validation.
Source generators provide a compile-time alternative for many mapping and serialization scenarios. They generate strongly typed code that performs the same property access without reflection overhead. If you control the types involved and the mapping pattern is repetitive, a source generator is often the better long-term choice.