reflection
nullable properties
programming
C#
software development

Find type of nullable properties via reflection

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

When you inspect properties through reflection in modern C#, there are two different nullable stories to handle. Nullable value types such as int? are represented directly in the runtime type system, while nullable reference types such as string? are mostly expressed through compiler-generated metadata.

Start With the Two Nullable Cases

For value types, reflection is straightforward. An int? property is really Nullable<int>, so Nullable.GetUnderlyingType can tell you whether a property is a nullable value type.

Reference types are different. string and string? both appear as System.String at runtime, so PropertyInfo.PropertyType alone cannot tell you whether the source code allowed null. For that case, use NullabilityInfoContext.

Detect Nullable Value Types

This helper handles value types first:

csharp
1using System;
2using System.Reflection;
3
4static bool IsNullableValueType(PropertyInfo property)
5{
6    return Nullable.GetUnderlyingType(property.PropertyType) != null;
7}

If the property type is int?, the helper returns true. If the property type is plain int, it returns false.

Detect Nullable Reference Types

For C# 8 and later, NullabilityInfoContext is the simplest reliable API. It reads the nullability metadata that the compiler emitted.

csharp
1#nullable enable
2using System;
3using System.Reflection;
4
5public class Person
6{
7    public string Name { get; set; } = "";
8    public string? Nickname { get; set; }
9    public int Age { get; set; }
10    public int? Score { get; set; }
11}
12
13public static class Program
14{
15    public static void Main()
16    {
17        var context = new NullabilityInfoContext();
18
19        foreach (var property in typeof(Person).GetProperties())
20        {
21            var info = context.Create(property);
22            var nullableValueType = Nullable.GetUnderlyingType(property.PropertyType);
23            var effectiveType = nullableValueType ?? property.PropertyType;
24
25            Console.WriteLine(
26                $"{property.Name} | type={effectiveType.Name} | " +
27                $"nullableRef={info.ReadState == NullabilityState.Nullable} | " +
28                $"nullableValueType={nullableValueType != null}");
29        }
30    }
31}

For Nickname, PropertyType is still String, but info.ReadState reports Nullable. For Score, Nullable.GetUnderlyingType reports Int32, which tells you the property is int?.

Return the Effective Type and Nullability Together

In many reflection-heavy systems, the practical goal is not just "is this nullable?" but "what underlying type should I treat this as?" A small helper can package both answers in one place.

csharp
1using System;
2using System.Reflection;
3
4static (Type EffectiveType, bool IsNullable) DescribeProperty(
5    PropertyInfo property,
6    NullabilityInfoContext context)
7{
8    var nullableValueType = Nullable.GetUnderlyingType(property.PropertyType);
9    if (nullableValueType != null)
10    {
11        return (nullableValueType, true);
12    }
13
14    var nullability = context.Create(property);
15    var isNullableReference =
16        !property.PropertyType.IsValueType &&
17        nullability.ReadState == NullabilityState.Nullable;
18
19    return (property.PropertyType, isNullableReference);
20}

This is a good shape for serializers, schema generators, or admin tooling that needs consistent metadata from reflected properties.

Conditions for Accurate Results

NullabilityInfoContext depends on compiler metadata. That means the project should have nullable annotations enabled, usually through #nullable enable in the file or the project-wide nullable setting in the .csproj. If the metadata is missing, reflection cannot reconstruct source-level intent reliably.

Also remember that generic types and nested members may need deeper inspection. For example, a List<string?> property is different from List<string>, and the nullability of the element type is separate from the nullability of the property itself.

Common Pitfalls

  • 'PropertyInfo.PropertyType cannot distinguish string from string?.'
  • 'Nullable.GetUnderlyingType only helps with Nullable<T>, not nullable reference types.'
  • If nullable annotations were disabled at compile time, reflection cannot tell you what the source intended.
  • Collections may have nullability rules for both the property and the contained element type.

Summary

  • Use Nullable.GetUnderlyingType for nullable value types such as int?.
  • Use NullabilityInfoContext for nullable reference types such as string?.
  • Treat value-type nullability and reference-type nullability as two separate reflection problems.
  • If you need runtime metadata for tooling, return both the effective type and the nullability flag together.

Course illustration
Course illustration

All Rights Reserved.