Properties, indexers, and operators provide C# with what some refer to as syntactic sugar. This means that they don’t provide functionality that couldn’t otherwise be achieved through methods. Instead, these elements allow a more succinct and familiar syntax to be used with objects and classes. When used judiciously, they can make code more intuitive and readable. In this article, I’ll introduce the Node and Path classes to illustrate properties, indexers, and operators. Node represents a named point in a two-dimensional space; a Path is an ordered collection of Nodes. Both classes override object.ToString for output.
Properties allow access to an object or class using simple field semantics while still maintaining encapsulation. A property may specify a valid combination of access modifiers and may be static. An instance property may be virtual or abstract. A property may override or hide a base property.
Like a field, a property specifies a type and a name. The name is followed by braces that contain the property’s accessors. An accessor is a subclause with no arguments that defines how the property is read or written. The get accessor returns a value of the property’s type, and the set accessor accepts a value of the property’s type through the keyword value. The correct accessor is called automatically based on how the property is used within a statement. For example, the set accessor is called if a property is used on the left side in assignment operator. The get accessor is called if the property is used on the right side in an assignment operator or as part of an expression.
A property may be made read-only or write-only by omitting the set or get accessor, respectively. In Listing A, the Node class defines the properties Name, X, and Y that manage the private fields m_name, m_x, and m_y.
Indexers allow access to an object using array element semantics. An indexer may specify a valid combination of access modifiers, may be virtual or abstract, or may override or hide a base indexer. However, an indexer may not be static. An indexer specifies a type but specifies the this keyword instead of a name. The indexer parameters follow, enclosed in brackets. As with properties, accessors follow the indexer parameters. The accessors use the parameters to identify the target element.
The Path class defines indexers to access nodes either by name or by coordinates. Path stores nodes in an instance of the .NET framework class ArrayList, located under the System.Collections namespace. ArrayList stores elements as object references and provides its own indexer to access elements by position. The Path indexer accessors use the private, overloaded IndexOf method to determine the element position and then return the target element, casting it back to Node. An exception is thrown if no node in the path matches the indexer parameters. See Listing B for an example.
C# supports unary, binary, and conversion operator overloading. This allows you to define specific operator behavior for your own types. All overloaded operators must specify the public and static modifiers and a return type. The following operators may be overloaded:
Unary: + - ! ~ ++ — true false
Binary: + - * / % & | ^ << >> == != > < >= <=
Conversion operators allow user-defined conversions to or from your type. The conversion may be either implicit or explicit. If a conversion results in no loss of data, it may be declared as implicit and the compiler will perform the conversion automatically. If data loss is possible, the conversion should be declared as explicit. The compiler will perform the conversion only when a cast is specified to signal consent.
The Node class defines an implicit conversion from Node to Path. The returned path contains a single node. The Path class overloads the binary + operator to concatenate two paths. The implicit conversion from Node to Path allows the Path + operator to concatenate a path and a node as well. The example in Listing C demonstrates the use of properties, indexers, and operators with nodes and paths.
Sweetening your program
The examples we’ve looked at here demonstrate the use of C# properties, indexers, and operators—but we could have used methods to achieve the same results. So is it worthwhile mastering these alternative techniques? Absolutely. They’ll help you create programs that are cleaner, slicker, and a bit more elegant.