Attributes and reflection have been part of C# from the beginning, and many frameworks still build on them today. Attributes let you declare intent directly on types, members, and parameters. Reflection lets runtime code inspect that metadata and act on it.
For this site, the important takeaway is practical scope: use reflection for startup discovery, diagnostics, or metadata-driven behavior, then cache the result. You do not need a deep metaprogramming system to get value from it.
Why it matters
This pair shows up behind routing, background jobs, serializers, ORMs, validators, and plugin loading. A small custom attribute can turn an otherwise manual registration step into a discoverable convention.
Used well, it keeps the metadata next to the code it describes and lets your runtime wire things together with less duplication.
Practical usage
A good default is to reflect once, near application startup, and convert the results into plain objects, delegates, or lookup tables that the hot path can use cheaply. That approach keeps the flexibility while avoiding repeated assembly scans.
It is also a strong fit for developer-facing features like help screens, diagnostics pages, and admin tooling where clarity matters more than raw throughput.
Cautions
Reflection is more fragile and more expensive than direct code paths, so avoid sprinkling it inside tight loops or per-request critical paths. Cache what you discover.
If you publish trimmed or Native AOT apps, be careful: reflection over members discovered only at runtime can require explicit annotations or a different design. Keep the scope bounded and predictable.
Discover handlers marked with a custom attribute
Attributes give you a simple opt-in marker, and reflection can build a registry once at startup.
Valid since C# 3.0
using System;
using System.Collections.Generic;
using System.Reflection;
[AttributeUsage(AttributeTargets.Class)]
public sealed class JobHandlerAttribute : Attribute
{
private readonly string _name;
public JobHandlerAttribute(string name)
{
_name = name;
}
public string Name
{
get { return _name; }
}
}
public interface IJobHandler
{
void Run();
}
[JobHandler("nightly-report")]
public sealed class NightlyReportJob : IJobHandler
{
public void Run()
{
Console.WriteLine("Generated the nightly report.");
}
}
[JobHandler("rebuild-search-index")]
public sealed class SearchIndexJob : IJobHandler
{
public void Run()
{
Console.WriteLine("Rebuilt the search index.");
}
}
public static class Program
{
public static void Main()
{
Dictionary<string, IJobHandler> handlers = new Dictionary<string, IJobHandler>();
Type[] discoveredTypes = Assembly.GetExecutingAssembly().GetTypes();
foreach (Type discoveredType in discoveredTypes)
{
if (!typeof(IJobHandler).IsAssignableFrom(discoveredType) || discoveredType.IsInterface || discoveredType.IsAbstract)
{
continue;
}
object[] attributes = discoveredType.GetCustomAttributes(typeof(JobHandlerAttribute), false);
if (attributes.Length == 0)
{
continue;
}
JobHandlerAttribute attribute = (JobHandlerAttribute)attributes[0];
handlers.Add(attribute.Name, (IJobHandler)Activator.CreateInstance(discoveredType));
}
handlers["nightly-report"].Run();
}
}
Generate help text from property attributes
Reflection is often most useful when it turns static metadata into a small runtime experience such as docs, help, or validation messages.
Valid since C# 3.0
using System;
using System.ComponentModel;
using System.Collections.Generic;
using System.Reflection;
public sealed class UserSettings
{
[Description("The time zone used when formatting dashboard timestamps.")]
public string TimeZone { get; set; }
[Description("When true, summaries are grouped into one daily email instead of immediate notifications.")]
public bool DigestEmails { get; set; }
}
public static class Program
{
public static void Main()
{
List<string> helpLines = new List<string>();
PropertyInfo[] properties = typeof(UserSettings).GetProperties();
foreach (PropertyInfo property in properties)
{
object[] attributes = property.GetCustomAttributes(typeof(DescriptionAttribute), false);
if (attributes.Length == 0)
{
continue;
}
DescriptionAttribute description = (DescriptionAttribute)attributes[0];
helpLines.Add(property.Name + ": " + description.Description);
}
Console.WriteLine(string.Join(Environment.NewLine, helpLines.ToArray()));
}
}
Related features
Learn more
Newer capabilities
-
Generic attributes
Introduced in C# 11.0
C# 11 adds generic attributes, which can remove a
typeof(...)argument when the attribute is really describing a type relationship. They build on the same metadata model shown here.