C# Reflection and Attributes: The Magic Mirror of Code 🪞
The Big Picture
Imagine you have a magic mirror. This mirror doesn’t show your face—it shows you everything about any object you look at. What it’s made of. What it can do. Even its secrets!
In C#, Reflection is that magic mirror. It lets your program look at itself and other programs. It can see classes, methods, and properties—even ones you didn’t know existed!
Attributes are like sticky notes you put on things. “This method is old!” or “Don’t use this!” They add extra information without changing what something does.
🌟 Reflection Basics: Looking in the Magic Mirror
What is Reflection?
Reflection is when your program can look at itself. It’s like a robot that can open its own chest and see all its parts.
Simple Example:
- You have a toy box (a class)
- Normally, you just play with toys
- With reflection, you can count the toys, read their labels, even find hidden toys!
Why Use Reflection?
- Discovery: Find out what’s inside a class you didn’t write
- Flexibility: Do things at runtime you didn’t plan at compile time
- Magic Tools: Build frameworks, serializers, and plugins
Getting Started
Everything starts with the Type class. It’s like getting the blueprint of any object.
// Get the type of any object
string name = "Hello";
Type myType = name.GetType();
Console.WriteLine(myType.Name);
// Output: String
You can also get a type without an object:
Type stringType = typeof(string);
Type intType = typeof(int);
🎯 Think of it like this:
GetType()asks “What are you?” andtypeof()asks “What is this thing called?”
🔍 Type Inspection: Reading the Blueprint
Now that we have the mirror, let’s see what it shows us!
Seeing Properties
Properties are like labels on a box. They tell you what’s inside.
Type personType = typeof(Person);
// Get all public properties
PropertyInfo[] props =
personType.GetProperties();
foreach (var prop in props)
{
Console.WriteLine(prop.Name);
}
Seeing Methods
Methods are like buttons on a remote. Each one does something.
Type personType = typeof(Person);
// Get all public methods
MethodInfo[] methods =
personType.GetMethods();
foreach (var method in methods)
{
Console.WriteLine(method.Name);
}
Seeing Fields
Fields are like secret pockets. They hold private data.
// Get private fields too!
FieldInfo[] fields = personType
.GetFields(BindingFlags.NonPublic
| BindingFlags.Instance);
The BindingFlags Toolbox
BindingFlags helps you filter what you see:
| Flag | What It Finds |
|---|---|
Public |
Public members |
NonPublic |
Private/protected |
Instance |
Regular members |
Static |
Static members |
Combine them with | (pipe):
BindingFlags all =
BindingFlags.Public
| BindingFlags.NonPublic
| BindingFlags.Instance;
🏠Dynamic Instantiation: Creating Objects at Runtime
Here’s where the magic gets really cool. You can create objects without knowing their type when you write the code!
Why Is This Useful?
Imagine a game with 100 enemy types. Instead of writing code for each one, you can say: “Create whatever enemy the player needs right now!”
Using Activator.CreateInstance
// Create an object from a Type
Type carType = typeof(Car);
object myCar = Activator
.CreateInstance(carType);
// Now myCar is a real Car!
Creating with Constructor Arguments
// Car has constructor: Car(string color)
object redCar = Activator
.CreateInstance(
typeof(Car),
new object[] { "Red" }
);
Creating from a Type Name String
This is super powerful for plugins and configuration:
// From full type name
Type type = Type.GetType(
"MyNamespace.Car"
);
object car = Activator
.CreateInstance(type);
Calling Methods Dynamically
Once you create an object, you can call its methods too:
// Get the method
MethodInfo driveMethod =
carType.GetMethod("Drive");
// Call it on our object
driveMethod.Invoke(myCar, null);
graph TD A["Get Type"] --> B["Create Instance"] B --> C["Get Method Info"] C --> D["Invoke Method"] style A fill:#e3f2fd style B fill:#bbdefb style C fill:#90caf9 style D fill:#64b5f6
🏷️ Attributes Basics: Sticky Notes for Code
What Are Attributes?
Attributes are like labels you stick on your code. They don’t change what the code does—they add extra information ABOUT the code.
Real Life Example:
- A jar has jam inside (the code)
- The label says “Strawberry, Made in 2024, Organic” (attributes)
- The label doesn’t change the jam—it describes it!
Creating an Attribute
// Simple attribute
[Serializable]
public class Person { }
// Attribute with value
[Obsolete("Use NewMethod instead")]
public void OldMethod() { }
Reading Attributes with Reflection
Here’s where reflection and attributes team up:
Type type = typeof(Person);
// Check if it has an attribute
bool isSerializable =
Attribute.IsDefined(
type,
typeof(SerializableAttribute)
);
// Get attribute details
var attrs = type
.GetCustomAttributes(true);
Making Your Own Attribute
You can create custom sticky notes!
// Define your attribute
public class AuthorAttribute
: Attribute
{
public string Name { get; }
public AuthorAttribute(string name)
{
Name = name;
}
}
// Use it
[Author("Alice")]
public class MyClass { }
📦 Built-in Attributes: The Ready-Made Labels
C# comes with many useful attributes. Here are the most important ones:
[Obsolete] - “This is Old!”
Tells developers: “Don’t use this anymore!”
[Obsolete("Use NewMethod()")]
public void OldMethod() { }
// Compiler shows warning!
[Serializable] - “Can Be Saved!”
Marks a class as saveable to files or network:
[Serializable]
public class GameSave
{
public int Level;
public int Score;
}
[Conditional] - “Only Sometimes!”
Method only runs in certain builds:
[Conditional("DEBUG")]
public void DebugLog(string msg)
{
Console.WriteLine(msg);
}
[AttributeUsage] - “Rules for Attributes!”
Controls where your custom attribute can go:
[AttributeUsage(
AttributeTargets.Class |
AttributeTargets.Method)]
public class MyAttribute
: Attribute { }
[Flags] - “Can Combine Values!”
For enums that can mix values:
[Flags]
enum Permissions
{
Read = 1,
Write = 2,
Execute = 4
}
// Can combine: Read | Write
Summary Table
| Attribute | What It Does |
|---|---|
[Obsolete] |
Marks old code |
[Serializable] |
Allows saving |
[Conditional] |
Debug-only code |
[AttributeUsage] |
Rules for attrs |
[Flags] |
Combinable enums |
🎯 Putting It All Together
Here’s a mini-story showing everything working together:
// 1. Create a class with attributes
[Author("Bob")]
[Serializable]
public class Hero
{
public string Name { get; set; }
[Obsolete("Use Attack()")]
public void Fight() { }
public void Attack() { }
}
// 2. Use reflection to explore it
Type heroType = typeof(Hero);
// 3. Create instance dynamically
object hero = Activator
.CreateInstance(heroType);
// 4. Find and read attributes
var author = heroType
.GetCustomAttribute<AuthorAttribute>();
Console.WriteLine(author.Name);
// Output: Bob
// 5. Call method dynamically
MethodInfo attack =
heroType.GetMethod("Attack");
attack.Invoke(hero, null);
graph TD A["Define Class + Attributes"] --> B["Get Type via Reflection"] B --> C["Read Attributes"] B --> D["Create Instance"] D --> E["Call Methods"] style A fill:#c8e6c9 style B fill:#a5d6a7 style C fill:#81c784 style D fill:#66bb6a style E fill:#4caf50
🚀 Key Takeaways
- Reflection = Looking at code structure at runtime
- Type Inspection = Discovering properties, methods, fields
- Dynamic Instantiation = Creating objects without knowing type ahead of time
- Attributes = Metadata labels attached to code
- Built-in Attributes = Ready-made labels like
[Obsolete]and[Serializable]
Remember the magic mirror analogy:
- Reflection is the mirror 🪞
- Type inspection is what you see in the mirror đź‘€
- Dynamic instantiation is creating what you see đźŹ
- Attributes are sticky notes on the things you see 🏷️
You now have the power to make your code look at itself—and even create new parts on the fly. That’s real magic! ✨
