Menu
Coddy logo textTech

Attributes and Reflection

Part of the Object Oriented Programming section of Coddy's C# journey — lesson 44 of 70.

Attributes are metadata tags you attach to code elements like classes, methods, or properties. They don't change how code executes directly, but they provide information that can be read at runtime. Reflection is the mechanism that reads this metadata.

You apply an attribute by placing it in square brackets above the target element:

[Obsolete("Use NewMethod instead")]
public void OldMethod() { }

[Serializable]
public class Person
{
    public string Name { get; set; }
}

You can create custom attributes by inheriting from Attribute:

public class AuthorAttribute : Attribute
{
    public string Name { get; }
    public AuthorAttribute(string name) => Name = name;
}

[Author("John")]
public class Calculator { }

Reflection lets you inspect types and read attributes at runtime using the System.Reflection namespace:

Type type = typeof(Calculator);
var attr = type.GetCustomAttribute<AuthorAttribute>();
Console.WriteLine(attr.Name);  // John

You can also discover methods, properties, and invoke them dynamically:

MethodInfo[] methods = type.GetMethods();
foreach (var method in methods)
    Console.WriteLine(method.Name);

Attributes and reflection power many frameworks - serialization libraries use them to control JSON output, testing frameworks use them to discover test methods, and dependency injection containers use them to wire up services automatically.

challenge icon

Challenge

Easy

Let's build a plugin discovery system that uses custom attributes and reflection to automatically find and describe classes marked as plugins. This is similar to how real frameworks discover components, test methods, or API endpoints at runtime.

You'll organize your code across three files:

  • PluginAttribute.cs: Create a custom attribute called PluginAttribute in the PluginSystem namespace. Your attribute should:
    • Inherit from Attribute
    • Have two read-only properties: Name (string) and Version (string)
    • Accept both values through its constructor
  • Plugins.cs: Create several plugin classes in the PluginSystem namespace. Some should be decorated with your PluginAttribute, and one should not:
    • ImageProcessor - marked with [Plugin("Image Processor", "1.0")]
    • DataExporter - marked with [Plugin("Data Exporter", "2.5")]
    • HelperUtility - NOT marked with any attribute (this is just a regular class)
    Each class can be empty - we're only interested in discovering their metadata through reflection.
  • Program.cs: In your main file, use reflection to discover all classes that have the PluginAttribute. For each discovered plugin, print its information. You'll need to:
    • Get all types from the current assembly
    • Check each type for the PluginAttribute
    • Extract and display the attribute's Name and Version properties

Use System.Reflection to inspect types and retrieve custom attributes. The GetCustomAttribute<T>() method returns null if the attribute isn't present, so you can use this to filter which classes are actual plugins.

Print each discovered plugin in this format, one per line:

Plugin: {Name} (v{Version})

The plugins should be printed in the order: ImageProcessor first, then DataExporter. The HelperUtility class should not appear in the output since it lacks the attribute.

Expected output:

Plugin: Image Processor (v1.0)
Plugin: Data Exporter (v2.5)

This challenge demonstrates how frameworks use attributes as markers and reflection to discover them - the same technique powers dependency injection containers, serialization libraries, and testing frameworks!

Cheat sheet

Attributes are metadata tags placed in square brackets above code elements. They provide information that can be read at runtime:

[Obsolete("Use NewMethod instead")]
public void OldMethod() { }

[Serializable]
public class Person
{
    public string Name { get; set; }
}

Create custom attributes by inheriting from Attribute:

public class AuthorAttribute : Attribute
{
    public string Name { get; }
    public AuthorAttribute(string name) => Name = name;
}

[Author("John")]
public class Calculator { }

Reflection lets you inspect types and read attributes at runtime using the System.Reflection namespace:

Type type = typeof(Calculator);
var attr = type.GetCustomAttribute<AuthorAttribute>();
Console.WriteLine(attr.Name);  // John

Discover methods and properties dynamically:

MethodInfo[] methods = type.GetMethods();
foreach (var method in methods)
    Console.WriteLine(method.Name);

The GetCustomAttribute<T>() method returns null if the attribute isn't present, allowing you to filter types based on attribute presence.

Try it yourself

using System;
using System.Reflection;
using PluginSystem;

class Program
{
    public static void Main(string[] args)
    {
        // TODO: Get all types from the current assembly
        // Hint: Use Assembly.GetExecutingAssembly().GetTypes()
        
        // TODO: Loop through each type and check if it has the PluginAttribute
        // Hint: Use type.GetCustomAttribute<PluginAttribute>()
        
        // TODO: If the attribute exists, print the plugin info in the format:
        // Plugin: {Name} (v{Version})
        
    }
}
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming