C# Attributes

Attributes are labels you attach to code elements — classes, methods, properties, or parameters — to add extra information (metadata) about them. This information can be read by the compiler, the runtime, or tools like test frameworks and serializers.

What Is Metadata?

┌──────────────────────────────────────────────────────────────┐
│  Normal code:     tells the computer WHAT TO DO              │
│  Metadata (attributes): tells tools HOW TO TREAT the code    │
│                                                              │
│  [Obsolete]         → "warn users this method is old"        │
│  [Serializable]     → "allow this class to be serialized"    │
│  [TestMethod]       → "treat this as a unit test"            │
│  [HttpGet]          → "this handles GET requests"            │
└──────────────────────────────────────────────────────────────┘

Using Built-In Attributes

[Obsolete] — Mark as Deprecated

class UserService
{
    [Obsolete("Use GetUserById() instead.")]
    public string GetUser(int id)
    {
        return "OldUser";
    }

    public string GetUserById(int id)
    {
        return "NewUser";
    }
}

class Program
{
    static void Main()
    {
        UserService svc = new UserService();

        // Compiler warning: 'GetUser is obsolete. Use GetUserById() instead.'
        string u = svc.GetUser(1);   // ⚠️ warning shown in IDE

        // Clean — no warning:
        string v = svc.GetUserById(1);
    }
}

[Obsolete] with Error Flag

[Obsolete("This method is removed. Use NewMethod().", true)]
// Second param = true → compile ERROR instead of warning
public void OldMethod() { }

[Serializable]

[Serializable]
class Config
{
    public string ServerName;
    public int Port;
}
// Allows this object to be serialized to XML or binary format

[Conditional] — Include Only in Debug Builds

using System.Diagnostics;

class Logger
{
    [Conditional("DEBUG")]
    public static void Log(string msg)
    {
        Console.WriteLine("[DEBUG] " + msg);
    }
}

// In Release builds, all calls to Log() are removed by the compiler
// In Debug builds, they run normally

Attribute Syntax

// Attribute with no arguments:
[Serializable]
class Order { }

// Attribute with one argument:
[Obsolete("Use NewMethod instead")]
public void OldMethod() { }

// Attribute with named arguments:
[Obsolete(message: "Deprecated", error: true)]
public void AnotherOldMethod() { }

// Multiple attributes:
[Serializable]
[Obsolete("Use the new Order class")]
class OldOrder { }

// Multiple on one line:
[Serializable, Obsolete("Old")]
class AnotherOldOrder { }

Creating a Custom Attribute

Custom attributes inherit from System.Attribute. You define them like a class with the data you want to store.

// Step 1: Define the attribute class
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
class AuthorAttribute : Attribute
{
    public string Name    { get; }
    public string Version { get; }

    public AuthorAttribute(string name, string version = "1.0")
    {
        Name    = name;
        Version = version;
    }
}

AttributeUsage Options

┌─────────────────────────────────────────────────────────────┐
│  [AttributeUsage(AttributeTargets.XXX)]                     │
├─────────────────────────────────────────────────────────────┤
│  AttributeTargets.Class     → apply to classes              │
│  AttributeTargets.Method    → apply to methods              │
│  AttributeTargets.Property  → apply to properties           │
│  AttributeTargets.Parameter → apply to parameters           │
│  AttributeTargets.All       → apply anywhere                │
└─────────────────────────────────────────────────────────────┘

Applying the Custom Attribute

[Author("Alice", "2.0")]
class OrderProcessor
{
    [Author("Bob")]
    public void ProcessOrder(int id)
    {
        Console.WriteLine("Processing order: " + id);
    }
}

Reading Attributes with Reflection

Attributes are read at runtime using Reflection. This is how frameworks like NUnit, ASP.NET, and Entity Framework discover and use your attributes.

using System;
using System.Reflection;

class Program
{
    static void Main()
    {
        Type type = typeof(OrderProcessor);

        // Read class-level attribute:
        AuthorAttribute classAttr = (AuthorAttribute)Attribute
            .GetCustomAttribute(type, typeof(AuthorAttribute));

        if (classAttr != null)
        {
            Console.WriteLine("Class Author: " + classAttr.Name);
            Console.WriteLine("Version: " + classAttr.Version);
        }

        // Read method-level attribute:
        MethodInfo method = type.GetMethod("ProcessOrder");
        AuthorAttribute methodAttr = (AuthorAttribute)Attribute
            .GetCustomAttribute(method, typeof(AuthorAttribute));

        if (methodAttr != null)
        {
            Console.WriteLine("Method Author: " + methodAttr.Name);
        }
    }
}
// Output:
// Class Author: Alice
// Version: 2.0
// Method Author: Bob

Real-World Example: Validation Attribute

[AttributeUsage(AttributeTargets.Property)]
class RequiredAttribute : Attribute
{
    public string ErrorMessage { get; }

    public RequiredAttribute(string errorMessage = "This field is required.")
    {
        ErrorMessage = errorMessage;
    }
}

class RegistrationForm
{
    [Required("Username is required.")]
    public string Username { get; set; }

    [Required]
    public string Email { get; set; }

    public string Nickname { get; set; }   // no attribute — optional
}

Common Built-In Attributes

┌───────────────────────────────┬──────────────────────────────────┐
│ Attribute                     │ Purpose                          │
├───────────────────────────────┼──────────────────────────────────┤
│ [Obsolete]                    │ Warn or error on usage           │
│ [Serializable]                │ Enable serialization             │
│ [Conditional("DEBUG")]        │ Compile conditionally            │
│ [DllImport]                   │ Link to external DLL function    │
│ [Flags]                       │ Enable bitwise enum operations   │
│ [NonSerialized]               │ Exclude field from serialization │
│ [ThreadStatic]                │ Make field unique per thread     │
│ [CallerMemberName]            │ Inject calling method name       │
│ [Required], [MaxLength] (ASP) │ Data annotation validation       │
└───────────────────────────────┴──────────────────────────────────┘

Quick Summary

┌──────────────────────────────────────────────────────────────┐
│  Attributes add metadata to code elements                    │
│  Placed in [square brackets] before the element              │
│                                                              │
│  Custom attribute:                                           │
│  class MyAttr : Attribute { public string Info; }            │
│                                                              │
│  Apply: [MyAttr("value")]                                    │
│                                                              │
│  Read: Attribute.GetCustomAttribute(memberInfo, typeof(MyAttr│
│                                                              │
│  Used heavily by: ASP.NET, EF, NUnit, JSON serializers       │
└──────────────────────────────────────────────────────────────┘

Attributes are the backbone of annotation-driven programming in C#. Once you understand them, the inner workings of ASP.NET routing, EF column mapping, JSON serialization, and unit test discovery all become clear — they are all driven by attributes and reflection working together.

Leave a Comment

Your email address will not be published. Required fields are marked *