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.
