Back to Blog
C#

C# Reflection: Get Attributes from Types and Members

c# reflection get attributes: Learn how to retrieve custom attributes from types and members using C# reflection, including inheritance behavior and performance consid...

ReflectionAttributesC#MetadataPerformance
Illustration of C# reflection retrieving attributes from a class member

When you need to read custom attributes from a type or member at runtime, reflection is the standard mechanism. The phrase "c# reflection get attributes" typically refers to using the System.Reflection APIs to query metadata attached to code elements. This article covers the core APIs, how they behave with inheritance, and where to be careful about performance.

The Core APIs for Retrieving Attributes

The simplest way to get a single attribute instance is Attribute.GetCustomAttribute. It takes a MemberInfo (which Type, MethodInfo, PropertyInfo, etc. inherit from) and the attribute type you're looking for. If the attribute is not present, it returns null.

Consider a custom attribute that records an author name:

[AttributeUsage(AttributeTargets.Class)] public class AuthorAttribute : Attribute { public string Name { get; } public AuthorAttribute(string name) => Name = name; } [Author("Jane Doe")] public class SampleService { }

To read it:

var type = typeof(SampleService); var author = (AuthorAttribute)Attribute.GetCustomAttribute(type, typeof(AuthorAttribute)); Console.WriteLine(author?.Name); // "Jane Doe"

If the attribute can appear multiple times, or you need to inspect all attributes on a member, use MemberInfo.GetCustomAttributes() instead. This returns an IEnumerable<Attribute> that you can filter or project.

var attributes = type.GetCustomAttributes(typeof(AuthorAttribute), inherit: true); foreach (AuthorAttribute attr in attributes) { Console.WriteLine(attr.Name); }

The inherit parameter controls whether inherited attributes are included, which we'll examine shortly.

Retrieving Attributes from Different Member Types

The same pattern works for methods, properties, fields, parameters, and even assembly-level attributes. For example, to get attributes from a method:

public class Service { [Author("John Smith")] public void Run() { } } var method = typeof(Service).GetMethod(nameof(Service.Run)); var methodAuthor = (AuthorAttribute)Attribute.GetCustomAttribute(method, typeof(AuthorAttribute));

For properties, you first obtain the PropertyInfo via GetProperty and then call GetCustomAttribute on it. The same applies to fields and parameters—each has its own MemberInfo subclass.

When dealing with multiple members of the same kind, you can iterate over the results of GetProperties(), GetMethods(), or GetFields() and inspect each one. This is common in serialization frameworks, validation libraries, and ORMs that map attributes to behavior.

Handling Inherited Attributes

Inheritance behavior is controlled by the AttributeUsageAttribute.Inherited property, which defaults to true for classes and methods. When you query a type that derives from a base class, GetCustomAttribute will also look at base types unless you pass inherit: false.

[AttributeUsage(AttributeTargets.Class, Inherited = true)] public class MarkerAttribute : Attribute { } [Marker] public class BaseClass { } public class DerivedClass : BaseClass { } var marker = Attribute.GetCustomAttribute(typeof(DerivedClass), typeof(MarkerAttribute)); // marker is not null, even though DerivedClass does not declare it

For methods, inheritance is more subtle. An attribute on a virtual method is inherited by an overriding method only if the attribute is marked Inherited = true and the overriding method does not explicitly declare the same attribute. If the method is not virtual, the attribute is not inherited. This can lead to surprising behavior when you expect attributes to flow down an override chain. Always test the specific scenario in your codebase.

Performance Considerations and Caching

Reflection calls are significantly slower than direct code access because they involve metadata lookup, type checks, and often allocation. Calling GetCustomAttribute repeatedly in a hot path can become a measurable overhead. The standard mitigation is to cache the result after the first lookup.

A simple approach is a static ConcurrentDictionary keyed by the member and attribute type:

private static readonly ConcurrentDictionary<(MemberInfo Member, Type AttributeType), Attribute?> Cache = new(); public static T? GetCachedAttribute<T>(MemberInfo member) where T : Attribute { var key = (member, typeof(T)); return (T?)Cache.GetOrAdd(key, k => Attribute.GetCustomAttribute(k.Member, k.AttributeType)); }

This reduces repeated reflection calls to a single dictionary lookup. For applications that query attributes frequently—such as a validation engine or a serialization layer—this caching pattern is essential.

Another consideration is that GetCustomAttributes allocates an array of Attribute objects. If you only need one attribute, prefer GetCustomAttribute to avoid unnecessary allocations. For multiple attributes, the array is unavoidable, but you can avoid re-fetching by storing the result.

Common Pitfalls and Edge Cases

One frequent mistake is forgetting that GetCustomAttribute returns null when the attribute is missing. Always null-check the result before casting, or use the as operator to avoid InvalidCastException.

Another edge case is when an attribute is applied multiple times. GetCustomAttribute returns only the first instance, so if you need all of them, use GetCustomAttributes and filter by type. Also, be aware that the order of attributes is not guaranteed by the runtime.

Attribute usage validation is performed at compile time only if you specify AttributeUsage correctly. If you attempt to apply an attribute to an invalid target, the compiler will reject it. However, reflection does not enforce this at runtime—you can query any attribute on any member, even if it was not intended for that target.

Finally, remember that Attribute.GetCustomAttribute has an overload that takes an Assembly and an Attribute type, which is useful for reading assembly-level attributes like AssemblyTitle or AssemblyVersion.

When to Use Reflection vs. Source Generators

Reflection is the classic way to read attributes, but it has runtime cost and no compile-time verification. In modern C#, source generators can inspect attributes at compile time and generate code that reads them without reflection. This is beneficial for scenarios like serialization, dependency injection, and logging, where the attribute set is known at compile time.

However, source generators require a separate project and more upfront complexity. Reflection remains the right choice when the attributes are discovered dynamically—for example, when loading plugins or when the set of types is not known until runtime. It also works with any .NET version without additional tooling.

A pragmatic approach is to use source generators for the hot paths and reflection for one-time initialization or dynamic scenarios. The decision depends on whether the attribute data is needed frequently and whether the cost of reflection is acceptable in your application's performance budget.

For most applications, the caching pattern described earlier reduces the performance impact to a negligible level. Start with reflection and a simple cache, then profile to see if a source generator is justified.

c# reflection get attributes: Practical Usage and Code Examp | RYUSLOG DEV