[C#] Defining Custom Attributes and Controlling Behavior with Reflection

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
    1. Inheriting the Attribute Class
    2. AttributeUsage Attribute
    3. 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 Enabled property) and use them to control your logic.
よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

私が勉強したこと、実践したこと、してることを書いているブログです。
主に資産運用について書いていたのですが、
最近はプログラミングに興味があるので、今はそればっかりです。

目次