BlackWaspTM

This web site uses cookies. By using the site you accept the cookie policy.This message is for compliance with the UK ICO law.

Reflection
/NET 1.1+

Creating Custom Attributes

The twenty-second and final part of the Reflection tutorial describes the creation and application of custom attributes. It also explains how those attributes can be configured to ensure that they are applied and inherited correctly.

Creating the Attributes

To begin, let's create the two attributes. The FixedColumnLine attribute is valid for classes or structures. It defines the total length of the fixed-column string that the type will generate. The FixedColumn attribute will be applied to properties that contain data to be included in the fixed-column string. This attribute includes a zero-based starting position at which to insert the data and the length of string that the property should create. If the string is shorter it will be padded and if longer it will be truncated.

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
public class FixedColumnLineAttribute : Attribute
{
    public FixedColumnLineAttribute(int width)
    {
        Width = width;
    }

    public int Width { get; set; }
}

[AttributeUsage(AttributeTargets.Property)]
public class FixedColumnAttribute : Attribute
{
    public FixedColumnAttribute(int start, int length)
    {
        Start = start;
        Length = length;
    }

    public int Start { get; set; }
    public int Length { get; set; }
}

Creating the Base Class

The base class is responsible for generating a fixed-column string from the properties of an object, using attributes to determine the formatting. All of this functionality is in the ToString method. This first calls GenerateBaseString, which creates a StringBuilder that is populated with spaces. The length of the string is obtained from the FixedColumnLine attribute.

Next, each property that has the FixedColumn attribute is added to the StringBuilder in the correct position. The value of the property is read using reflection, padded and truncated, to ensure it is the length specified in the attribute, and then inserted into the StringBuilder. Once the StringBuilder is complete, it is converted to a string and returned.

The code for the base class is as follows:

public abstract class FixedColumnLineBase
{
    public override string ToString()
    {
        StringBuilder sb = GenerateBaseString();
        InsertProperties(sb);
        return sb.ToString();
    }

    private StringBuilder GenerateBaseString()
    {
        FixedColumnLineAttribute attribute =
            Attribute.GetCustomAttribute(GetType(), typeof(FixedColumnLineAttribute))
            as FixedColumnLineAttribute;

        return new StringBuilder(new string(' ', attribute.Width));
    }

    private void InsertProperties(StringBuilder sb)
    {
        PropertyInfo[] properties = GetType().GetProperties();
        foreach (PropertyInfo property in properties)
        {
            InsertProperty(sb, property);
        }
    }

    private void InsertProperty(StringBuilder sb, PropertyInfo property)
    {
        FixedColumnAttribute attribute =
            Attribute.GetCustomAttribute(property, typeof(FixedColumnAttribute))
            as FixedColumnAttribute;

        if (attribute != null)
        {
            string value = property.GetValue(this, null).ToString();
            value = value.PadRight(attribute.Length);
            value = value.Substring(0, attribute.Length);
            sb.Remove(attribute.Start, attribute.Length);
            sb.Insert(attribute.Start, value);
        }
    }
}

NB: No validation code has been included in this abstract class to keep the example concise.

Creating The Sample Class

The base class and the attributes now allow us to easily create classes from which fixed-column lines can be generated. One such class is shown below. Instances of the Person class can generate a fixed length string of forty characters. The FirstName property will be included as twenty characters, starting from position zero. The LastName property is also twenty characters and will be inserted at positions 20 to 39.

[FixedColumnLine(40)]
public class Person : FixedColumnLineBase
{
    [FixedColumn(0, 20)]
    public string FirstName { get; set; }

    [FixedColumn(20, 20)]
    public string LastName { get; set; }
}

Testing the Fixed-Column String

To show the results of the overridden ToString method we can now create a Person object, populate its properties and output the result of calling ToString. In the sample below, note the positions of apostrophes that show that the correct number of spaces are included as padding.

Person person = new Person();
person.FirstName = "Frank";
person.LastName = "Black";
Console.WriteLine("'{0}'", person);

/* OUTPUT

'Frank               Black               '

*/
27 August 2012