Reflection (part 2, advanced), attributes

In my post Reflection I covered simple reflection to follow up on generic classes.

Each application in the .Net environment is not only consisting of the program code itself, it also consists of information about the code. This information is stored in the so-called metadata. I am going to show how to read metadata attributes at runtime today.

Attributes can be applied to: Assemblies, classes, class members, constructors, delegates, enums, events, fields, interfaces, methods, modules, parameters, properties, return values and structs. You can read attributes at runtime and act accordingly. They are like information tags.

Maybe a metaphor helps to describe attributes easily. When you go shopping and buy food you can find additional information on nearly all products:
– weight
– price
– expiry date
– nutritional values (eg. vitamins, proteins, fat, sugar)
– the brand
– any information the producer wants to let you know

This is only additional data. This is not the product itself.

Attributes do not change the code execution unless actively intended by a programmer. Some attributes are read by compilers (eg. [Flags] for enum), some by applications or libraries (eg. in JSON [DataContract]).
In source codes attributes are placed in square brackets [] above the declaration that you want the attributes to apply to. For instance the often applied [Serializable] attribute of the SerializableAttribute class indicates that a type can be serialized to be stored or transmitted somewhere else.

[Serializable]
class Animal {
    public string Name { get; set; }
    public int Age { get; set; }
} 

You can use multiple attributes and some can even be applied multiple times. By convention, all attribute names end with the word Attribute to distinguish them from other items. However, you do not need to specify the Attribute suffix when using attributes in source codes. For example, you can specify DescriptionAttribute as follows:

[Serializable]
[Description("http://www.GreenPeace.com")]
class Animal {
    public string Name { get; set; }
    public int Age { get; set; }
} 

// same as the following syntax
[Serializable, Description("http://www.GreenPeace.com")]
class Animal {
    public string Name { get; set; }
    public int Age { get; set; }
} 

// same as the following syntax
[Serializable, DescriptionAttribute("http://www.GreenPeace.com")]  // [Description] == [DescriptionAttribute]
class Animal {
    public string Name { get; set; }
    public int Age { get; set; }
}

// a custom attribute that would be used multiple times
[Changes(Version=1.1, Change="Added Age")]
[Changes(Version=1.2, Change="Added Gender")]
class Animal {
    public enum eGender { male, female, dunno };

    public string Name { get; set; }
    public int Age { get; set; }
    public eGender Gender { get; set; }
} 

You can specify the target of an attribute explicitly. The following code applies all attributes to the current assembly.

[assembly: AssemblyTitle("HelloWorld")]
[assembly: AssemblyDescription("Chapter1")]
[assembly: AssemblyCompany("Hardcore Ltd.")]

The System.Attribute class is the lowest base class for all attribute classes. It has a few static methods.

[Serializable]
public class Animal {
    public string Name { get; set; }
    public int Age { get; set; }

    public static bool IsSerializable {
        get { return Attribute.IsDefined(typeof(Animal), typeof(SerializableAttribute)); }  // returns true
    } //
} // class

How to read the property of attributes?

[Description("Only cute dogs"), Serializable]
public class Dogs {
    public static void PrintProperties() {
        Attribute[] lAttributes = Attribute.GetCustomAttributes(typeof(Dogs)) as Attribute[];
        foreach (Attribute lAttribute in lAttributes) {
            Console.WriteLine(lAttribute.GetType().Name.ToString());
            if (lAttribute is DescriptionAttribute) Console.WriteLine(((DescriptionAttribute)lAttribute).Description);
        }
        Console.ReadLine();
    } //
} // class

example output:
SerializableAttribute
DescriptionAttribute
Only cute dogs

To build our own attribute we have to inherit from an existing attribute, which is at least System.Attribute. The next code example is more complex. We use multiple attributes for AnyClass.Method3() and permit this with AllowMultiple = true in our AttributeUsage. On the same line we limit the usage of our custom attribute to classes, methods and properties. The ProgrammerAttribute class does nothing else than storing the values. There is no extensive code as you might expect from real program code.
By calling typeof(AnyClass).GetMethods() we enter the method level. The flags are set the way that we receive public and private methods. The result also includes inherited methods.

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Property, AllowMultiple = true)]
public class ProgrammerAttribute : Attribute {
    private readonly DateTime _Date;
    private readonly string _Name;
    private readonly string _Info;

    public ProgrammerAttribute(int xYear, int xMonth, int xDay, string xName, string xInfo) {
        _Name = xName;
        _Date = new DateTime(xYear, xMonth, xDay);
        _Info = xInfo;
    } // constructor

    public string Name { get { return _Name; } }
    public string Info { get { return _Info; } }
    public DateTime Date { get { return _Date; } }
} //

[Serializable]
[Programmer(2014, 1, 9, "Bastian M.K. Ohta", "added")]
public class AnyClass {
    [Programmer(2014, 1, 10, "Bastian M.K. Ohta", "bug fix: threading issue solved")]
    public double Method1(double x) { return 2 * x; }

    [Programmer(2014, 1, 12, "Bastian M.K. Ohta", "added")]
    public int Method2() { return 0; }

    [Programmer(2014, 1, 12, "Bastian M.K. Ohta", "under construction")]
    [Programmer(2014, 1, 14, "Bastian M.K. Ohta", "Programmer on vacation until Friday.")]
    private void Method3(double x) { }
} //


public static void Print(ProgrammerAttribute[] xProgrammers) {
    foreach (ProgrammerAttribute p in xProgrammers) {
        Console.WriteLine(p.Date.ToString("dd MMM yy ") + p.Name + ", " + p.Info);
    }    
} //

public static void Test() {
    ProgrammerAttribute[] lProgrammers;

    Console.WriteLine("Class level:");
    lProgrammers = Attribute.GetCustomAttributes(typeof(AnyClass), typeof(ProgrammerAttribute)) as ProgrammerAttribute[];
    Print(lProgrammers);

    Console.WriteLine(Environment.NewLine + "Methods:");
    MethodInfo[] lMethodInfos = typeof(AnyClass).GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
    //MethodInfo[] lMethodInfos = typeof(AnyClass).GetMethods(BindingFlags.Instance | BindingFlags.Public); // filter: only public methods
    foreach (MethodInfo m in lMethodInfos) {
        Console.WriteLine("Found method " + m.Name);
        lProgrammers = m.GetCustomAttributes(typeof(ProgrammerAttribute) , false) as ProgrammerAttribute[];
        Print(lProgrammers);
        Console.WriteLine();
    }

    Console.ReadLine();
} //

example output:
Class level:
09 Jan 14 Bastian M.K. Ohta, added

Methods:
Found method Method1
10 Jan 14 Bastian M.K. Ohta, bug fix: threading issue solved

Found method Method2
12 Jan 14 Bastian M.K. Ohta, added

Found method Method3
12 Jan 14 Bastian M.K. Ohta, am on vacation until Friday
12 Jan 14 Bastian M.K. Ohta, under construction

Found method ToString

Found method Equals

Found method GetHashCode

Found method GetType

Found method Finalize

Found method MemberwiseClone

That’s it for today. No worries, there will be many follow-ups on reflection.

Advertisements

About Bastian M.K. Ohta

Happiness only real when shared.

Posted on January 10, 2014, in Advanced, C#, Reflection and tagged , , , , , , , . Bookmark the permalink. Leave a comment.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: