Class Attributes — Digitteck
Class Attributes
dotnet·4 October 2018·4 min read

Class Attributes

Reflection lets you associate metadata with types and query it at runtime through the IL/MSIL metadata stored in the portable executable (PE) file. Custom attributes provide a clean way to attach such metadata — here, to tag filter classes with their data type category so the category is declared in one place and read everywhere else.

Example 1 — Without Attributes

Each filter class defines its category as an abstract property override, and the strategy dictionary re-states it as a key. Two definitions of the same fact:

csharp
// Without attributes — the data type category is hard-coded in each class
public abstract class FilterClassic
{
    public abstract FilterDataType FilterDataType { get; }
    public abstract bool Test(object a);
}

public class FilterClassicIsInteger : FilterClassic
{
    // Category defined here...
    public override FilterDataType FilterDataType => FilterDataType.Integer;

    public override bool Test(object a)
    {
        try   { Convert.ToInt32(a);  return true; }
        catch { return false; }
    }
}

public class FilterClassicIsText : FilterClassic
{
    public override FilterDataType FilterDataType => FilterDataType.Text;

    public override bool Test(object a)
    {
        try   { Convert.ToString(a); return true; }
        catch { return false; }
    }
}
csharp
// ...and the same category must be repeated in the dictionary — two definitions, one truth.
static Dictionary<FilterDataType, Func<FilterClassic>> filters =
    new Dictionary<FilterDataType, Func<FilterClassic>>
    {
        { FilterDataType.Integer, () => new FilterClassicIsInteger() },
        { FilterDataType.Text,    () => new FilterClassicIsText()    },
    };
// Adding a new filter type requires updating both the class AND this dictionary.

Example 2 — With Attributes

Defining a custom attribute is as simple as inheriting from System.Attribute. A small helper class reads it back via GetCustomAttribute<T>():

csharp
// Define the attribute — inherit from System.Attribute
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
public class FilterDataTypeAttribute : Attribute
{
    public FilterDataType FilterDataType { get; }

    public FilterDataTypeAttribute(FilterDataType filterDataType)
    {
        FilterDataType = filterDataType;
    }
}

// Helper that reads the attribute from a given type via reflection
public class FilterMetadata
{
    public FilterDataType FilterDataType { get; private set; }

    public FilterMetadata(Type t)
    {
        FilterDataType = t.GetCustomAttribute<FilterDataTypeAttribute>().FilterDataType;
    }
}

The base class reads its own attribute in the constructor. Concrete subclasses only declare the category once — as a decoration:

csharp
// The base class reads its own type's attribute in the constructor
public abstract class Filter
{
    public FilterMetadata FilterMetadata { get; private set; }
    public abstract bool Test(object a);

    protected Filter()
    {
        // Read the [FilterDataType(...)] attribute from the concrete subclass
        FilterMetadata = new FilterMetadata(this.GetType());
    }
}

// Category is now declared once — on the class, not duplicated in a dictionary
[FilterDataType(FilterDataType.Text)]
public class FilterIsText : Filter
{
    public override bool Test(object a)
    {
        try   { Convert.ToString(a); return true; }
        catch { return false; }
    }
}

[FilterDataType(FilterDataType.Integer)]
public class FilterIsInteger : Filter
{
    public override bool Test(object a)
    {
        try   { Convert.ToInt32(a);  return true; }
        catch { return false; }
    }
}

Usage with Attributes

The registration helper infers the key from the type parameter so the dictionary is populated without repeating the category:

csharp
// Dictionary is built by reading the attribute — no duplicate category definition
static Dictionary<FilterDataType, Func<Filter>> filtersWithMeta =
    new Dictionary<FilterDataType, Func<Filter>>();

// Helper reads the FilterDataType attribute from the type parameter T
static void AddFilter<T>(Func<T> generator) where T : Filter
{
    var key = new FilterMetadata(typeof(T)).FilterDataType;
    filtersWithMeta.Add(key, generator);
}

// Visual Studio infers T from the lambda — explicit type parameter is optional
AddFilter(() => new FilterIsText());     // key = FilterDataType.Text
AddFilter(() => new FilterIsInteger());  // key = FilterDataType.Integer

// Adding a new filter type: create the class with [FilterDataType(...)], call AddFilter — done.

Tags

.NETC#AttributesReflectionDesign Patterns
digitteck

© 2026 Digitteck