IN THE COURSE OF DEVELOPING an application, it is quite typical to have core functionality contained in methods that are invoked by other specialized methods. This reduces the spread of code and improves the maintainability of the code base. Similarly, it is routine to move commonly used data to other generalized levels. However, sometimes it becomes apparent that the shared implementations or the data should be published to a level where the implementations or data can be used across other class definitions. This is where metadata comes into play, as object-related services consume this information to provide reusable implementations. In this chapter, you'll get a tour of the fundamentals of attributes. You'll see how code is dissected to determine when attributes should be used and when other techniques are more applicable. Then you'll be provided with the essentials of attribute-based programming in .NET. You'll understand how attributes are defined and where they can be applied in an assembly. Finally, you'll get a detailed look at where the attribute's information is stored in an assembly. Applications of Metadata Attributes are just like any other tool in the developer's proverbial toolkit. They are useful in some situations; in other cases, they can make the solution much more complex than it needs to be. In the following sections, we'll describe how data is used within code and when data is used to define and describe the code base itself. Defining Data in the Code Let's start our journey into metadata by looking at some rather simplistic models of a country. The intent is not to have a class that completely describes a country, but to focus on what the code does. Listing 1-1 shows an initial attempt at coding a class that defines a country. As you'll see in a moment, it's not the finest example of writing code. Defining Data in the CodeLet's start our journey into metadata by looking at some rather simplistic models of a country. The intent is not to have a class that completely describes a country, but to focus on what the code does. Listing 1-1 shows an initial attempt at coding a class that defines a country. As you'll see in a moment, it's not the finest example of writing code.Listing 1-1. Defining the BadCountry Classnamespace Apress.NetAttributes{ public class BadCountry { public string mName; public long mPopulation; public BadCountry() : base() { } public long Population { get { return this.mPopulation; } set { if (value < 0 || value > 5000000000000) { throw new ArgumentOutOfRangeException( "value", value, "The given value is out of range."); } this.mPopulation = value; } } public string Name { get { return this.mName; } set { this.mName = value; } } }}Seasoned developers can probably find a number of problems with this implementation. However, the key points of contention with this code as it relates to data quality are as follows: The mName field is never initialized. If you create an instance of the BadCountry class and get the Name property, you will have a null reference, which may cause a NullReferenceException. Whenever the Population property is set, the given value is checked to ensure it is within a predefined range. However, this range is parameterized via hard-coded values (zero and five trillion inclusive). If you wanted to change the range's boundary conditions, you would need to find these values within each occurrence of the range check. Furthermore, there is no way for a BadCountry client to know that the range exists without resorting to reverse-engineering techniques. The exception message is hard-coded. If you wanted to change the message, you would need to do this within the property setter itself. Also, there is no way to reuse the message string in another section of code. The fields are declared as public, so any data validation that exists (such as the Population setter) can be circumvented with ease. To address these issues, Listing 1-2 shows a second attempt at constructing a country definition.
To address these issues, Listing 1-2 shows a second attempt at constructing a country definition.
Listing 1-2. Improving the BadCountry Definitionnamespace Apress.NetAttributes{ public class ImprovedCountry { private const string ERROR_ARGUMENT_NAME = "name"; private const string ERROR_ARGUMENT_POPULATION = "population"; private const string ERROR_MESSAGE_NULL_VALUE = "The given value should not be null."; private const string ERROR_MESSAGE_OUT_OF_RANGE = "The given value is out of range."; public const long MINIMUM_POPULATION = 0; public const long MAXIMUM_POPULATION = 5000000000000; protected string mName = string.Empty; protected long mPopulation; private ImprovedCountry() : base() { } public ImprovedCountry(string name, long population) { this.CheckInvariantName(name); this.CheckInvariantPopulation(population); this.mName = name; this.mPopulation = population; } protected void CheckInvariantName(string name) { if (name == null) { throw new ArgumentNullException( ImprovedCountry.ERROR_ARGUMENT_NAME, ImprovedCountry.ERROR_MESSAGE_NULL_VALUE); } } protected void CheckInvariantPopulation(long population) { if (population < ImprovedCountry.MINIMUM_POPULATION || population > ImprovedCountry.MAXIMUM_POPULATION) { throw new ArgumentOutOfRangeException( ImprovedCountry.ERROR_ARGUMENT_POPULATION, population, ImprovedCountry.ERROR_MESSAGE_OUT_OF_RANGE); } } public long Population { get { return this.mPopulation; } set { this.CheckInvariantPopulation(value); this.mPopulation = value; } } public string Name { get { return this.mName; } set { this.CheckInvariantName(value); this.mName = value; } } }}