Master of Dev
Share

An Introduction to C# Attributes and Runtime Reflection

Published

Hey there! As I’ve had some questions from my friends about this, I’ve decided to write a basic guide to C# attributes, C# reflection and potential use cases for both. For the sake of definition, ‘reflection’ means the ability for code to look at or ‘reflect’ on itself, also often known as introspection.


These techniques tend to be used for metaprogramming, where the program can examine itself and dynamically modify its own properties without any special hard code to do it (Other than some basic reflection). This means that you can then add another class and your reflection system should [hopefully] be able to automagically handle the new class without any new code. Attributes allow you to then be able to tag things in your classes, including the class itself, fields and properties, methods and even method parameters! A reflection system can then get these attributes and handle things in different ways. Unity is a great example of this. One Unity attribute allows you to mark a static method in such a way that a button is added to a toolbar using properties you specify within the attribute. Clicking that button then runs that method. Another when applied to a class that inherits from ScriptableObject adds a context menu item that allows you to create an instance of that class as an asset.

And Unity is an example of one of the major use cases for reflection and attributes: Editors. Editors, whether for games, word processing or otherwise, tend to be expandable by users other than yourself, whether through co-workers, plugins or otherwise. In C#, someone could write a new class for a game object or other primitive, and the editor automatically populates its list of spawnables, as well as its properties window when an instance is selected. You can tag static methods as being callable from the toolbar or a context menu, and you can tag which fields should be saved without having to write fiddly save/load logic on a per class basis.

So, what do this magical fantastical attributes look like. If you’ve ever worked in C# or Unity, you’ll may have experienced this before in something like:

public class SomeBehaviour : MonoBehaviour 
{ 
    [Serializable] 
    private float someValue = 13.37f; 
}

Here, the attribute is the word Serializable in the square brackets before someValue. For Unity, this makes someValue viewable in the editor, even though it’s private, as well as allowing someValue to be saved and loaded to and from the scene – something that doesn’t normally happen to private variables. Some attributes can also accept parameters as they are technically constructors, and they can run their own logic if need be (although most of them just store their values in public fields). For example, in Unity (Again), you can do:

public class SomeEditorTools : MonoBehaviour 
{ 
    [MenuItem("Editor Menu/Do A Thing")] 
    private static void DoAThing() 
    { 
        Debug.Log("I did a thing!"); 
    } 
}

Here, the attribute MenuItem takes a string being the path for a menu item. When Unity recompiles this script and looks for these attributes, the path will be interpreted and a new menu category called ‘Editor Menu’ will be created, with one button within called ‘Do A Thing’. Clicking that button then runs the DoAThing() method. Notice how DoAThing() is private, yet appears to be accessed from outside the class. The previous example also had this. How is this possible? You wouldn’t normally be able to access private members in anything but the same class! Luckily(?) for us, reflection systems can bypass the protection systems that are in place for normal code. This gives reflection a lot of power (Which is why it’s sort of hidden away), but for good reason. Like the serializable example, you might want to be able to save things to disk that you don’t want normal code accessing in any situation. Someone desperate enough can find their way around it – hence why unobfuscated C# is not secure – but it prevents people from doing things inadvertently and bypassing checks you have elsewhere.

So, where to start? I guess creating your own attribute would be a good way to go, so lets do that. It’s pretty easy, you just have to inherit from the Attribute class! Let’s create an attribute to mark which fields to save. We can do that like this:

public class SaveableAttribute : Attribute 
{ 
}

Bam. You’ve just made a new attribute! It’s highly recommended that you always end the class name with ‘Attribute’, but don’t worry, you don’t need to type that in the actually attribute when you tag something. This is mainly done to prevent name-conflicts with non-attribute classes. If we wanted to accept parameters, like an enum to say how it saves or a bool to indicate that it also saves on autosaves, we’d do something like:

public class SaveableAttribute : Attribute 
{ 
    public bool DontSaveWithAutosaves { get; private set; } 
 
    public SaveableAttribute(bool dontSaveWithAutosaves = false) 
    { 
        DontSaveWithAutosaves = dontSaveWithAutosaves; 
    } 
}

Here, we just accept a parameter (and can give it a default to make it optional) and set a public property of field so it can be read later. The rules for this are the same as any standard class, and you can even put methods in there (Although anything other than getter methods don’t tend to be useful). This is the attribute we’ll be using from now on.

Now, to use the attribute, we’ll just tag something with it. Here’s an example class:

public class SomeObject 
{ 
    [Saveable] 
    public float a = 13.37f; 
 
    public int b = 9001; 
 
    [Saveable(true)] 
    public string c = "Test"; 
}

Here, field a is saveable at all times, c is saveable but only when not autosaving, and b is not saveable at all. Notice how we didn’t type the full SaveableAttribute name – the C# compiler will automatically handle the ‘Attribute’ suffix for us. These attributes mean nothing though if we don’t have code to handle them, so lets do that!

using System.Reflection; 
 
public class SaveManager 
{ 
    public string GetSaveStringFor(object obj, bool isAutoSave) 
    { 
        StringBuilder saveString = new StringBuilder(); 
        SaveableAttribute saveableAttr; 
 
        saveString.AppendLine($"[{obj.GetType().Name}]"); 
        foreach (FieldInfo fieldInfo in obj.GetType().GetFields()) 
        { 
            saveableAttr = fieldInfo.GetCustomAttribute(); 
 
            if (saveableAttr != null && (!isAutoSave || !saveableAttr.DontSaveWithAutosaves)) 
            { 
                saveString.AppendLine($"{fieldInfo.Name}={fieldInfo.GetValue(obj)}"); 
            } 
        } 
 
        return saveString.ToString(); 
    } 
}

Now this meaty bit of code does a few things. First off, to use reflection in your code, you need using System.Reflection; at the top of your file. This gives us access to a number of key reflection classes such as FieldInfo. If you’re using Visual Studio, it will shout at you pretty sharpish and provide the intellisense prompt to automatically add it. Now, this SaveManager class is just an example, requiring all fields to have an appropiate ToString() method, but this is good enough to demonstrate. We create a string builder as we’re going to be doing a lot of concatenating for this, as well as create a variable to store a SaveableAttribute (This is just so we don’t keep on allocating more memory for each iteration of the loop). Then we append some form of string into the loop to identify the object type (Reflection allows you to create an object from a string as well, so this is handy), then we start a foreach loop. FieldInfo is a metadata class, holding all the low level information about a field in a class (Fields being basic variables. Those variables with those funky get/set parts are called properties and use PropertyInfo instead, and you can probably surmise that methods use MethodInfo) and from a FieldInfo we can grab all attributes attached to a class. Now, the list of FieldInfo‘s are stored within a classes Type info (This is just an instance of the Type class), which contains all potential information about a type and anything within it. Therefore, we get the type from the object instance via GetType(), then get all the fields from that type with GetFields(). Pretty simple.

Now, in the loop, we call GetCustomAttribute<>() on the FieldInfo we’re currently on, which allows us to grab an attribute by type. If the FieldInfo doesn’t have that attribute, then it returns null instead, allowing us to ignore this FieldInfo. In this instance, we also check to see if we’re either not autosaving, or if we are, the attribute lacks the DontSaveWithAutosaves flag. If it passes this check, then we can append a line to the save string, being the FieldInfo‘s name (Literally the name you gave the variable) and the value that was stored in that field in obj. This is why obj is passed as a parameter to GetValue, the FieldInfo is part of the type of the class, and is not specific to any one instance. Now, if we run this test code (Assuming you’re in a console application):

class Program 
{ 
    static void Main(string[] args) 
    { 
        SaveManager saveManager = new SaveManager(); 
 
        SomeObject objA = new SomeObject(); //Will stay default 
        SomeObject objB = new SomeObject(); //Will be changed 
        SomeObject objC = new SomeObject(); //Will also be changed 
 
        //Change all of objB''s properties 
        objB.a = 42.0f; 
        objB.b = -1; 
        objB.c = "This was changed!"; 
 
        //Change all of objC's properties too, but this will be saved as with  
        //autosave marked true so c shouldn't be saved at all 
        objB.a = 3.14159f; 
        objB.b = 5000000; 
        objC.c = "This was changed for objC!"; 
 
        //Save A normally 
        Console.WriteLine(saveManager.GetSaveStringFor(objA, false)); 
        //Save B normally 
        Console.WriteLine(saveManager.GetSaveStringFor(objB, false)); 
        //Save C as if it were an autosave 
        Console.WriteLine(saveManager.GetSaveStringFor(objC, true)); 
 
        Console.ReadLine(); 
    } 
}

Then you should get the following output:

[SomeObject] 
a=13.37 
c=Test 
 
[SomeObject] 
a=3.14159 
c=This was changed! 
 
[SomeObject] 
a=13.37 

Notice how we’re missing b entirely from all three objects (It was never marked Saveable). Also note how the second object’s values are different from the first’s, while the third object – saved with isAutosave set to true – is also lacking any value for c. This is only scratching the surface for what’s possible with reflection and attributes. For example, MethodInfo – a type that stores all info regarding a method, shockingly – has an Invoke method that can call that method on an object (or none at all if it’s static), allowing you to automatically do things like create UI elements. As mentioned before, you can create an instance of an object by having it’s type name as a string, then loop through all it’s fields and properties loading in data saved to disk. The possibilities are vast, if somewhat complex.

As a bit of an anecdote, one project had me integrating Clojure into a data analysis tool written in C#. As many types and methods already existed in C#, it ended up being easier to tag methods with attributes saying that they’re callable in Clojure (as well as giving them a Clojure friendly name), and having a reflection function generate Clojure code that invokes the C# code. This also made making plugins much easier, as the plugin developer could just write in C#, and the Clojure frontend could automatically work with it.

Anyway, I hope that I helped give you a very basic understanding of how C# attributes and reflection work (These should also work in VB.NET and other .NET languages with some different syntax, so give it a try!). Until next time!