C# attributes
custom attributes
attribute constructor
.NET programming
software development

When is a custom attribute's constructor run?

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

A common misconception in C# is that a custom attribute's constructor runs the moment the attribute is applied to a class, method, or property. In reality, the constructor is only invoked when something actively reads the attribute through reflection, typically via GetCustomAttributes(). This lazy-instantiation design means attributes have zero runtime cost until they are explicitly inspected. Understanding this timing is crucial for writing correct attribute-based frameworks and avoiding unexpected behavior.

Defining a Custom Attribute

To create a custom attribute, you derive from System.Attribute and optionally define a constructor to accept metadata values:

csharp
1using System;
2
3[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
4public class AuditAttribute : Attribute
5{
6    public string Reviewer { get; }
7    public DateTime CreatedAt { get; }
8
9    public AuditAttribute(string reviewer)
10    {
11        Reviewer = reviewer;
12        CreatedAt = DateTime.UtcNow;
13        Console.WriteLine($"AuditAttribute constructor called for reviewer: {reviewer}");
14    }
15}

The Console.WriteLine inside the constructor helps you observe exactly when instantiation happens.

When the Constructor Actually Runs

Applying the attribute to a class does not trigger the constructor:

csharp
1[Audit("Alice")]
2public class InvoiceService
3{
4    [Audit("Bob")]
5    public void ProcessInvoice() { }
6}

At this point, the attribute metadata is stored in the assembly's IL (Intermediate Language) as a blob of bytes, but no AuditAttribute object has been created. The constructor runs only when you retrieve the attribute through reflection:

csharp
1using System;
2using System.Reflection;
3
4class Program
5{
6    static void Main()
7    {
8        Console.WriteLine("Before GetCustomAttributes");
9
10        // This triggers the AuditAttribute constructor for InvoiceService
11        var classAttrs = typeof(InvoiceService).GetCustomAttributes(typeof(AuditAttribute), false);
12
13        Console.WriteLine("Between calls");
14
15        // This triggers the AuditAttribute constructor for ProcessInvoice
16        MethodInfo method = typeof(InvoiceService).GetMethod("ProcessInvoice");
17        var methodAttrs = method.GetCustomAttributes(typeof(AuditAttribute), false);
18
19        Console.WriteLine("After GetCustomAttributes");
20    }
21}

The output is:

 
1Before GetCustomAttributes
2AuditAttribute constructor called for reviewer: Alice
3Between calls
4AuditAttribute constructor called for reviewer: Bob
5After GetCustomAttributes

Each GetCustomAttributes call instantiates a fresh attribute object. If you call it twice, the constructor runs twice -- there is no caching by default.

Scanning Attributes Across an Assembly

Frameworks like ASP.NET and xUnit scan entire assemblies for attributes at startup. You can do the same:

csharp
1using System;
2using System.Reflection;
3
4class Program
5{
6    static void Main()
7    {
8        Assembly assembly = Assembly.GetExecutingAssembly();
9
10        foreach (Type type in assembly.GetTypes())
11        {
12            // Each call instantiates the attribute
13            var attrs = type.GetCustomAttributes(typeof(AuditAttribute), false);
14            foreach (AuditAttribute attr in attrs)
15            {
16                Console.WriteLine($"{type.Name} reviewed by {attr.Reviewer}");
17            }
18        }
19    }
20}

This pattern is how dependency injection containers discover services, how test runners find test fixtures, and how serialization libraries detect configuration. In all cases, the attribute constructor only runs during the reflection scan, not when the decorated class is compiled or loaded.

Checking for an Attribute Without Instantiation

If you only need to know whether an attribute is present -- without running its constructor -- use Attribute.IsDefined():

csharp
bool hasAudit = Attribute.IsDefined(typeof(InvoiceService), typeof(AuditAttribute));
Console.WriteLine(hasAudit); // True, but constructor was NOT called

This reads the metadata from the IL without creating an instance. It is faster and avoids any side effects the constructor might have.

Common Pitfalls

  • Assuming the constructor runs at decoration time. The attribute constructor does not execute when you compile or load the class. It only runs when reflection reads it via GetCustomAttributes. Placing initialization logic in the constructor that you expect to "always run" will silently do nothing unless something inspects the attribute.
  • Putting expensive work in the attribute constructor. Because GetCustomAttributes creates a new instance on every call, expensive operations (database lookups, file I/O) in the constructor will run repeatedly. Move heavy logic out of the constructor and into the consuming framework.
  • Forgetting that each GetCustomAttributes call creates a new instance. Two calls return two distinct objects. If you mutate a property on one instance, the change is not reflected in the next retrieval. Treat attribute instances as short-lived, read-only metadata.
  • Not specifying AttributeUsage on your custom attribute. Without [AttributeUsage(...)], the attribute can be applied anywhere and multiple times, which may not match your intent. Always specify valid targets and whether multiple instances are allowed.
  • Relying on constructor side effects for application behavior. Since the constructor only runs during reflection, any side effect (logging, registration, counter increment) depends on whether and when something reads the attribute. This makes behavior unpredictable and hard to test.

Summary

  • A custom attribute's constructor runs only when reflection reads it (for example, via GetCustomAttributes()), not when the attribute is applied to a code element.
  • Each call to GetCustomAttributes creates a new instance of the attribute, invoking the constructor each time.
  • Use Attribute.IsDefined() to check for an attribute's presence without instantiating it.
  • Keep attribute constructors lightweight -- avoid side effects and expensive operations.
  • Always declare [AttributeUsage] to constrain where and how many times your attribute can be applied.

Related reading
Course
Intermediate
27 lessons
14 hours
OOD Fundamentals

Master object-oriented design from first principles, SOLID, design patterns, and classic interview problems with hands-on coding.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.