FREE BOOK

Chapter I: Attribute Fundamentals

Posted by Apress Free Book | C# Language December 09, 2008
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.

At first glance, it looks like there is more code involved, and that is true. There are 39 lines in the BadCountry definition and 72 lines in the ImprovedCountry definition. However, the code base has been vastly improved. The fields are now protected, so any changes to their values must be done through the Application Programming Interface (API) set up by ImprovedCountry1 or via inheritance.

Furthermore, any time the field values need to be changed, validation rules are enforced via calls to either CheckInvariantName() or CheckInvariantPopulation() in the constructor and the setters of both properties. The error messages are now stored in constants and are available for reuse in future version of ImprovedCountry.Finally, the minimum and maximum population values are stored as public constants, so clients will know what the range is before they write even one line of code against an ImprovedCountry instance.

All of these improvements in the code are fairly standard in any refactoring phase. However, these changes don't necessarily make the code bulletproof. We think it's a good idea to move so-called magic values (values that just pop up in code) into constants. But the problem with constants is that they are not automatically updated in client code. For example, let's say there was aWinForm application that used the ImprovedCountry class, and it showed the minimum and maximum population values to the user, like this:

namespace Apress.NetAttributes
{
    public class CountryClient : System.Windows.Forms.Form
    {
        private System.Windows.Forms.Label lblMaximumPopulationValue;
        private System.Windows.Forms.Label lblMinimumPopulationValue;


        public CountryClient()
        {
            InitializeComponent();
            this.ShowCountry();
            this.ShowPopulationLimits();
        }
        private void ShowPopulationLimits()
        {
            this.lblMaximumPopulationValue.Text =
            ImprovedCountry.MAXIMUM_POPULATION.ToString();
            this.lblMinimumPopulationValue.Text =
            ImprovedCountry.MINIMUM_POPULATION.ToString();
        }
        static void Main()
        {
            Application.Run(new CountryClient());
        }
        // ...
    }
}

Let's say that population increases and improvements in space exploration now require us to change the maximum population.2

public const long MAXIMUM_POPULATION = 10000000000000;

We now take version 2.0.0.0 of the Country assembly and put it into the CountryClient's application directory. Figure 1-1 shows the results of running the application.



Figure 1-1. Displaying constant values in an updated assembly

The results may surprise you at first glance. Why didn't the client application pick up the new boundary value? The reason is that constant values are embedded into a client assembly at compile-time. Take a look at the resulting Common Intermediate Language (CIL) code3 that is in ShowPopulationValues().

.method private hidebysig instance
    void ShowPopulationLimits() cil managed
{
    .maxstack 2
    .locals init (int64 V_0)
    IL_0000: ldarg.0
    IL_0001: ldfld class [System.Windows.Forms]System.Windows.Forms.Label
    Apress.NetAttributes.CountryClient::lblMaximumPopulationValue
    IL_0006: ldc.i8 0x48c27395000
    IL_000f: stloc.0
    IL_0010: ldloca.s V_0
    IL_0012: call instance string [mscorlib]System.Int64::ToString()
    IL_0017: callvirt instance void [System.Windows.Forms]
    System.Windows.Forms.Control::set_Text(string)
    IL_001c: ldarg.0
    IL_001d: ldfld class [System.Windows.Forms]System.Windows.Forms.Label
    Apress.NetAttributes.CountryClient::lblMinimumPopulationValue
    IL_0022: ldc.i4.0
    IL_0023: conv.i8
    IL_0024: stloc.0
    IL_0025: ldloca.s V_0
    IL_0027: call instance string [mscorlib]System.Int64::ToString()
    IL_002c: callvirt instance void [System.Windows.Forms]
    System.Windows.Forms.Control::set_Text(string)
    IL_0031: ret
}

The lines that are of particular importance are IL_0006 and IL_0022. The value, 0x48c27395000, is 5,000,000,000,000, and ldc.i4.0 loads 0 onto the stack. Essentially, the constant values are hard-coded into the client's code base, so any updates go unnoticed.

Again, constants are not necessarily a bad thing. But creating public constants does not mean that clients will ever use those constants in the way that the implementer intended. Naming a field MAXIMUM_POPULATION has some humanbased semantics,5 but it's up to a client to use that field effectively. Moreover, as you have just seen, constants do not have a good versioning story. Sometimes, it's advantageous to have data that can be versioned and is accessible to a client. As you'll see in the "Attributes in .NET" section later in this chapter, attributes have these properties.

Total Pages : 7 12345

comments