In C#, in addition to standard attributes (like [Obsolete] or [Serializable]), developers can define their own attributes (custom attributes).
By using this, you can attach unique metadata (marks such as “this item is required” or “automatically convert this item”) to classes or properties. You can then build systems that read this information at runtime using Reflection to perform special processing.
Table of Contents
- Implementation Example of Custom Attributes
- Sample Code
- Execution Result
- Explanation and Technical Points
- Inheriting the Attribute Class
- AttributeUsage Attribute
- Reading with Reflection
Implementation Example of Custom Attributes
Here, I will use a “User Profile” class for a web service as an example.
I will create a custom attribute named [AutoEmpty] to perform the process of “automatically converting the value to an empty string ("") if it is null.” I will then implement logic that rewrites values only for properties marked with this specific attribute.
Sample Code
using System;
using System.Reflection;
public class Program
{
public static void Main()
{
// 1. Create test data
// Website and Bio are unset (null)
var user = new UserProfile
{
UserName = "GuestUser001",
WebsiteUrl = null,
Bio = null,
PrivateMemo = null
};
Console.WriteLine("--- Before Processing ---");
user.PrintState();
// 2. Convert null to empty string only where the custom attribute is attached
ApplyNullToEmptyRule(user);
Console.WriteLine("\n--- After Processing ---");
user.PrintState();
}
/// <summary>
/// Scans properties in the object. If [AutoEmpty] is attached
/// and enabled (Enabled=true), it replaces null with an empty string.
/// </summary>
public static void ApplyNullToEmptyRule<T>(T obj)
{
if (obj == null) return;
Type type = typeof(T);
// Get list of properties
var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var prop in properties)
{
// Skip non-string types
if (prop.PropertyType != typeof(string)) continue;
// Skip read-only properties
if (!prop.CanWrite) continue;
// 3. Get the custom attribute [AutoEmpty]
var attr = prop.GetCustomAttribute<AutoEmptyAttribute>();
// Skip if the attribute is missing or disabled (Enabled=false)
if (attr == null || !attr.Enabled)
{
continue;
}
// If the current value is null, set it to empty string
var currentValue = prop.GetValue(obj);
if (currentValue == null)
{
prop.SetValue(obj, string.Empty);
Console.WriteLine($"[Conversion Executed] Converted {prop.Name} to empty string.");
}
}
}
}
// ---------------------------------------------------------
// 1. Define the custom attribute class
// Inherit from Attribute. Convention is to name it "Name + Attribute"
// ---------------------------------------------------------
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class AutoEmptyAttribute : Attribute
{
// Flag to enable/disable conversion
public bool Enabled { get; }
// Constructor
public AutoEmptyAttribute(bool enabled = true)
{
Enabled = enabled;
}
}
// ---------------------------------------------------------
// 2. Data class using the attribute
// ---------------------------------------------------------
public class UserProfile
{
// No attribute: Not a target for conversion
public string UserName { get; set; }
// Attribute present (true): Converted to empty string if null
[AutoEmpty(true)]
public string WebsiteUrl { get; set; }
// Attribute present (default is true): Converted
[AutoEmpty]
public string Bio { get; set; }
// No attribute: Should remain null
public string PrivateMemo { get; set; }
public void PrintState()
{
Console.WriteLine($"Name : <{UserName ?? "null"}>");
Console.WriteLine($"Web : <{WebsiteUrl ?? "null"}>");
Console.WriteLine($"Bio : <{Bio ?? "null"}>");
Console.WriteLine($"Memo : <{PrivateMemo ?? "null"}>");
}
}
Execution Result
--- Before Processing ---
Name : <GuestUser001>
Web : <null>
Bio : <null>
Memo : <null>
[Conversion Executed] Converted WebsiteUrl to empty string.
[Conversion Executed] Converted Bio to empty string.
--- After Processing ---
Name : <GuestUser001>
Web : <>
Bio : <>
Memo : <null>
Explanation and Technical Points
1. Inheriting the Attribute Class
To create a unique attribute, inherit from the System.Attribute class. It is standard convention to add Attribute to the end of the class name (e.g., AutoEmptyAttribute). When using it, you can omit the suffix and write it as [AutoEmpty].
2. AttributeUsage Attribute
Use [AttributeUsage] to restrict “where” the custom attribute can be attached.
- AttributeTargets.Property: Allows attachment only to properties. Attempting to attach it to a class or method will result in a compilation error.
- AllowMultiple = false: Prohibits attaching this attribute multiple times to the same property.
3. Reading with Reflection
Use prop.GetCustomAttribute<T>() to retrieve the attribute.
- If the return value is null: The attribute is not attached to that property.
- If the return value is an instance: The attribute is attached. You can read the values set in the constructor (such as the
Enabledproperty) and use them to control your logic.
