CHAPTER 19 Properties in C#.

Afew months ago, writing some code, we came up with a situation where one of the fields in a class (Filename) could be derived from another (Name). We therefore decided to use the prop­erty idiom (or design pattern) in C++ and wrote a getFilename() function for the field that was derived from the other. We then had to walk through all the code and replace the reference to the field with calls to getFilename(). This took a while, since the project was fairly big.

We also had to remember that when we wanted to get the filename, we had to call the getFilename() member function, rather than merely referring to the Filename member of the class. This made the model a bit tougher to grasp; instead of Filename just being a field, we had to remember that we were really calling a function whenever we needed to access it. Similarly, we needed to call a setFilename() function to set the value of filename.

That getFilename() and setFilename() are logically related to a single “virtual field” isn’t obvious when looking at a class, especially when class members are listed alphabetically, as they usually are. The property pattern is good for the author of a class but a bit clunky for the user of the class.

C# adds properties as first-class citizens of the language. Properties appear to be fields to the user of a class, but they use a block of code (known as an accessor) to get the current value and set a new value. You can separate the user model (a field) from the implementation model (a member function), which reduces the amount of coupling between a class and the users of a class, leaving more flexibility in design and maintenance.

In the .NET runtime, properties are implemented using a naming pattern and a little bit of extra metadata linking the member functions to the property name. This allows properties to appear as properties in some languages and merely as member functions in other languages.

Properties are used heavily throughout the .NET Framework; in fact, there are few (if any) public fields.

1. Accessors

A property consists of a property declaration and either one or two blocks of code—known as accessors[1]—that handle getting or setting the property. Here’s a simple example:

class Test {

private string name;

public string Name {

get

{

return name;

}

set

{

name = value;

}

}

}

This class declares a property called Name and defines both a getter and a setter for that property. The getter merely returns the value of the private variable, and the setter updates the internal variable through a special parameter named value. Whenever the setter is called, the variable value contains the value to which the property should be set. The type of value is the same as the type of the property.

Properties can have a getter, a setter, or both. A property that has only a getter is called a read-only property, and a property that has only a setter is called a write-only property.

2. Properties and Inheritance

Like member functions, properties can also be declared using the virtual, override, or abstract modifiers. These modifiers are placed on the property and affect both accessors.

When a derived class declares a property with the same name as in the base class, it hides the entire property; it isn’t possible to hide only a getter or setter.

3. Use of Properties

Properties separate the interface of a class from the implementation of a class. This is useful when the property is derived from other fields and when you want to do lazy initialization and fetch a value only if the user really needs it.

Suppose that a carmaker wanted to be able to produce a report that listed some current information about the production of cars:

using System; class Auto {

public Auto(int id, string name)

{

this.id = id;

this.name = name;

}

// query to find # produced public int ProductionCount {

get

{

if (productionCount == -1)

{

// fetch count from database here.

}

return(productionCount);

}

}

public int SalesCount {

get

{

if (salesCount == -1)

{

// query each dealership for data

}

return(salesCount);

}

}

string name; int id;

int productionCount = -1; int salesCount = -1;

}

Both the ProductionCount and SalesCount properties are initialized to -1, and the expensive operation of calculating them is deferred until it’s actually needed.

4. Side Effects When Setting Values

Properties are also useful to do something beyond merely setting a value when the setter is called. A shopping basket could update the total when the user changed an item count, for example:

using System; using System.Collections; class Basket {

internal void UpdateTotal()

{

total = 0;

foreach (BasketItem item in items)

{

total += item.Total;

}

}

ArrayList items = new ArrayList();

Decimal total;

}

class BasketItem {

BasketItem(Basket basket)

{

this.basket = basket;

}

public int Quantity

{

get

{

return(quantity);

}

set

{

quantity = value; basket.UpdateTotal();

}

}

public Decimal Price {

get

{

return(price);

}

set

{

price = value;

basket.UpdateTotal();

}

}

public Decimal Total {

get

{

// volume discount; 10% if 10 or more are purchased if (quantity >= 10)

return(quantity * price * 0.90m);

else

return(quantity * price);

}

}

int quantity; // count of the item

Decimal price; // price of the item

Basket basket; // reference back to the basket

}

In this example, the Basket class contains an array of BasketItem. When the price or quantity of an item is updated, an update is fired back to the Basket class, and the basket walks through all the items to update the total for the basket.

You could also implement this interaction more generally using events, which are covered in Chapter 24.

5. Static Properties

In addition to member properties, C# also allows the definition of static properties, which belong to the whole class rather than to a specific instance of the class. Like static member functions, static properties can’t be declared with the virtual, abstract, or override modifiers.

When we discussed readonly fields in Chapter 8, we showed one case that initialized some static readonly fields. You can do the same thing with static properties without having to initialize the fields until necessary. The value can also be fabricated when needed and not stored. If creating the field is costly and it will likely be used again, then the value should be cached in a private field. If it’s cheap to create or it’s unlikely to be used again, it can be created as needed:

class Color {

public Color(int red, int green, int blue) {

this.red = red;

this.green = green;

this.blue = blue;

}

int         red;

int         green;

int         blue;

public static Color Red {

get

{

return(new Color(255, 0, 0));

}

}

public static Color Green {

get

{

return(new Color(0, 255, 0));

}

}

public static Color Blue {

get

{

return(new Color(0, 0, 255));

}

}

}

class Test {

static void Main()

{

Color background = Color.Red;

}

}

6. Property Efficiency

Returning to the first example in this chapter, consider the efficiency of the code when executed:

class Test {

private string name;

public string Name {

get

{

return name;

}

set

{

name = value;

}

}

}

This may seem to be an inefficient design, because a member function call is added where you’d normally see a field access. However, there’s no reason the underlying runtime environment can’t inline the accessors as it would any other simple function, so there’s often no performance penalty in choosing a property instead of a simple field. The opportunity to be able to revise the implementation later without changing the interface can be invaluable, so properties are usually a better choice than fields for public members.

A small downside does remain when using properties; they aren’t supported natively by all .NET languages, so other languages may have to call the accessor functions directly, which is a bit more complicated than using fields.

7. Property Accessibility

During the initial design of the C# language, the language designers thought that allowing different accessibility levels for get and set accessors was an unnecessary complexity. After a considerable amount of customer feedback, the designers decided to reverse this decision for C# 2.0.

The problem with a uniform accessibility level was that in many cases a public get accessor and an internal, protected, or private set accessor was required. C# 1.0 didn’t support this, which resulted in code that looked something like this:

class Test {

private string name; public string Name {

get

{

return name;

}

}

protected void SetName(string name) {

this.name = name;

}

}

In this sample, a pseudo-setter is declared outside the property declaration just to allow a different accessibility level to be defined. It was decided that this ugly work-around was worse than the added complexity that different accessibility can cause, and the property syntax was extended to allow accessibility levels for an accessor to be “overridden.” Rewriting the Test class correctly, it becomes the following:

class Test {

private string name; public string Name {

get

{

return name;

}

protected set {

name = value;

}

}

}

Accessibility modifiers specified at the accessor level can restrict the accessibility level only below that specified at the property level. Therefore, if a property is declared as protected, it causes a compiler error to specify an accessor as having public visibility.

You can apply an accessibility modifier to either the get or the set, not to both. This means the most visible accessor must have that accessibility level specified at the property level, and a modifier can then be placed on either the get method or the set method—whichever has the stricter accessibility requirement.

8. Virtual Properties

If a property makes sense as part of the base class, it may make sense to make the property virtual. Virtual properties follow the same rules as other virtual entities. Here’s a quick example of a virtual property:

using System;

public abstract class DrawingObject {

public abstract string Name {

get;

}

}

class Circle: DrawingObject {

string name = “Circle”;

public override string Name {

get

{

return(name);

}

}

}

class Test {

public static void Main()

{

DrawingObject d = new Circle();

Console.WriteLine(“Name: {0}”, d.Name);

}

}

The abstract property Name is declared in the DrawingObject class, with an abstract get accessor. This accessor must then be overridden in the derived class.

When the Name property is accessed through a reference to the base class, the overridden property in the derived class is called.

Source: Gunnerson Eric, Wienholt Nick (2005), A Programmer’s Introduction to C# 2.0, Apress; 3rd edition.

Leave a Reply

Your email address will not be published. Required fields are marked *