Using C# Attributes

By wyldsider

We have all done it before. As part of some program, we need to load a series of values. In the TNG Screensaver project, for the three base screensavers, I had the following:
SizeOfCells = AnimationSettings.FetchSetting(AnimationName, "SizeOfCells", 30);
ShowMazeGeneration = AnimationSettings.FetchSetting(AnimationName, "ShowMazeGeneration", false);
ShowMazeSolution = AnimationSettings.FetchSetting(AnimationName, "ShowMazeSolution", true);

Pretty basic, but pretty error prone. If I decide to add another option, say the pause time between parts of the animation, I would have to remember to add in the line as above, and another one where I save the options. Is there a better way? In c#, there is… its called an attribute.

To accomplish this, I created a base class for all of the types called AnimationOptionAttribute which is descended from Attribute and defines a single property OptionName to allow us to override the name of the option that we are settings or getting. I then created a new class BooleanAnimationOptionAttribute
which looks as follows:
/// <summary>
/// Class to represent a decorator attribute for boolean animation options.
/// </summary>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple=false )]
class BooleanAnimationOptionAttribute : AnimationOptionAttribute
{
/// <summary>
/// Keep track of the default value for this attribute.
/// </summary>
public Boolean DefaultValue { get; set; }

/// <summary>
/// Create an attribute as directed by the user.
/// </summary>
/// <param name="optionName">Name of the option we are representing.</param>
/// <param name="defaultValue">Default Boolean value for this item.</param>
public BooleanAnimationOptionAttribute(Boolean defaultValue)
: base()
{
DefaultValue = defaultValue;
}
}

As you can see, this simply defines the attribute, allowing for a default value. This is a good start, but now we have to figure out how to search for that. Luckily, after a google search or two, I stumbled across two separate pages for attributes and reflection. To find the information we need, we are going to have to use reflection to scan an instance of an object for particular entities, in our case fields and properties, and look for any of them that contain one of our attributes. As we will be compiling a list, we need something to store them in.
/// <summary>
/// Data class to hold on to attributes that we have been asked to discover.
/// </summary>
public class DiscoveredAttributes
{
/// <summary>
/// Attribute that we found.
/// </summary>
public Attribute AttributeFound { get; set; }

/// <summary>
/// Member that the attribute was found attached to.
/// </summary>
public MemberInfo MemberAttachedTo { get; set; }
/// <summary>
/// Simple constructor.
/// </summary>
/// <param name="attributeDiscovered">Atttribute that we discovered.</param>
/// <param name="attachedMember">Member it was attached to.</param>
public DiscoveredAttributes(Attribute attributeDiscovered, MemberInfo attachedMember)
{
AttributeFound = attributeDiscovered;
MemberAttachedTo = attachedMember;
}
}

This data class will accomplish our needs, as all we need is what was found and what it was attached to. Now on to our method to discover fields. First, the method.
/// <summary>
/// Search for any fields that have one of the attributes we are looking for attached to it.
/// </summary>
/// <param name="objectToSearch">Object to search for attributes.</param>
/// <returns>List of discovered attributes.</returns>
public static List<DiscoveredAttributes> SearchForFieldsWithAttribute<T>(Object objectToSearch) where T : System.Attribute
{
// We will always return a list, possibly containing only 0 elements.
List<DiscoveredAttributes> returnCode = new List<DiscoveredAttributes>();

// Look for all of the fields within the object, and for each of them look for all of the custom
// properties that match our type and add them to our list.
FieldInfo[] fieldMemberInfos = objectToSearch.GetType().GetFields();
foreach (FieldInfo nextFieldInfo in fieldMemberInfos)
{
foreach (Attribute nextAttribute in nextFieldInfo.GetCustomAttributes(typeof(T), false))
{
DiscoveredAttributes newDiscovery = new DiscoveredAttributes(nextAttribute, nextFieldInfo);
returnCode.Add(newDiscovery);
}
}
return returnCode;
}

When you look at it, a number of things will pop out. The first is that I have chosen to use generics for the method declaration, and I am restricting the type. This is a useful feature of generics that I like about c#. In this case, we only want to use this for Attributes, so I add the restricting that T must be an Attribute. Then we use discovery to find out all of the fields for the object that was passed in. When we find a field, we check the custom attributes for one of the attributes that we are looking for, and if we find it, we add it to the list. I also coded one for properties, as I like using the C# 3.0 automated properties feature, and it is pretty much the same as above.

Now we can see the finish line… we have a way to tag the fields and properties we want to load, we have a way of creating a list of matching attributes, so all we need to do is to apply the information we have collected.
/// <summary>
/// Try and automatically load as many of the options as is possible.
/// </summary>
protected void AutoLoadOptions()
{
// Get a list of all of the attributes that we can find.
List<AttributeHelper.DiscoveredAttributes> listOfAttributes =
AttributeHelper.SearchForFieldsWithAttribute <AnimationOptionAttribute>(this);
listOfAttributes.AddRange( AttributeHelper.SearchForPropertiesWithAttribute<AnimationOptionAttribute>(this));

// Go through our list looking for the fields and properties that we need to fetch the values for.
foreach (AttributeHelper.DiscoveredAttributes nextDiscovery in listOfAttributes)
{
// Assume we have a boolean value for now, and grab the value.
BooleanAnimationOptionAttribute booleanOption = nextDiscovery.AttributeFound as BooleanAnimationOptionAttribute;
Boolean optionValue = AnimationSettings.FetchSetting(AnimationName, nextDiscovery.MemberAttachedTo.Name, booleanOption.DefaultValue);

// figure out if we need to set this to a property or a field.
PropertyInfo memberProperty = nextDiscovery.MemberAttachedTo as PropertyInfo;
if (memberProperty != null)
{
memberProperty.SetValue(this, optionValue,null);
}
FieldInfo memberField = nextDiscovery.MemberAttachedTo as FieldInfo;
if (memberField != null)
{
memberField.SetValue(this, optionValue);
}
}
}

This is currently set up for the Boolean attribute we defined early, but you should get the idea. We go through our list, and figure out what type of an attribute we have. We then grab the default value and use it to grab the information from our lower level routine to fetch our settings. Once we have the value, we can then figure out whether we have a property or a field and set the value into that.

Is this a lot of work? Somewhat. But it simplifies the fetching of the data and makes it so that we tie to location of the field/property to its setting location where it is defined, eliminating the chance that we will forget to load it. Saving the value is pretty similar, and adding should be simple to. This code is part of the TNG screensaver project, so check it out to see how it evolves.

Tags: , ,

Leave a Reply