Defining Properties
Properties are defined in a similar way to fields, but there ’ s more to them. Properties, as already discussed, are more involved than fields in that they can perform additional processing before modifying state — and, indeed, might not modify state at all. They achieve this by possessing two function – like blocks: one for getting the value of the property and one for setting the value of the property.
These blocks, also known as accessors, defined using get and set keywords, respectively, may be used to control the access level of the property. You can omit one or the other of these blocks to create read – only or write – only properties (where omitting the get block gives you write – only access, and omitting the set block gives you read – only access). Of course, that only applies to external code because code elsewhere within the class will have access to the same data that these code blocks have. You can also include accessibility modifiers on accessors — making a get block public while the set block is protected, for example. You must include at least one of these blocks to obtain a valid property (and, let ’ s face it, a property you can ’ t read or change wouldn ’ t be very useful).
The basic structure of a property consists of the standard access modifying keyword ( public , private , and so on), followed by a type name, the property name, and one or both of the get and set blocks that contain the property processing:
public int MyIntProp { get { // Property get code. } set { // Property set code. } }
The first line of the definition is the bit that is very similar to a field definition. The difference is that there is no semicolon at the end of the line; instead, you have a code block containing nested get and set blocks.
get blocks must have a return value of the type of the property. Simple properties are often associated with a single private field controlling access to that field, in which case the get block may return the field ’ s value directly:
// Field used by property. private int myInt; // Property. public int MyIntProp { get { return myInt; } set { // Property set code. } }
Code external to the class cannot access this myInt field directly due to its accessibility level (it is private). Instead, external code must use the property to access the field. The set function assigns a value to the field similarly. Here, you can use the keyword value to refer to the value received from the user of the property:
// Field used by property. private int myInt; // Property. public int MyIntProp { get { return myInt; } set { myInt = value; } }
value equates to a value of the same type as the property, so if the property uses the same type as the field, then you never have to worry about casting in situations like this. This simple property does little more than shield direct access to the myInt field. The real power of properties is apparent when you exert a little more control over the proceedings. For example, you might implement your set block as follows:
set { if (value > = 0 && value <= 10) myInt = value; }
Here, you modify myInt only if the value assigned to the property is between 0 and 10. In situations like this, you have an important design choice to make: What should you do if an invalid value is used? You have four options:
- Do nothing (as in the preceding code).
- Assign a default value to the field.
- Continue as if nothing had gone wrong but log the event for future analysis.
- Throw an exception.
In general, the last two options are preferable. Deciding between them depends on how the class will be used and how much control should be assigned to the users of the class. Exception throwing gives users a fair amount of control and lets them know what is going on so that they can respond appropriately.
You can use one of the standard exceptions in the System namespace for this:
set { if (value > = 0 && value <= 10) myInt = value; else throw (new ArgumentOutOfRangeException("MyIntProp", value, "MyIntProp must be assigned a value between 0 and 10.")); }