The accessor-declarations of a property specify the executable statements associated with reading and writing the property.
The accessor declarations consist of a get-accessor-declaration, a set-accessor-declaration, or both. Each accessor declaration consists of an optional accessor-modifier, followed by the token get
or set
, followed by an accessor-body. For abstract
accessors, the accessor-body is simply a semicolon. For all other accessors, the accessor-body is a block which specifies the statements to execute when the accessor is invoked.
A get
accessor corresponds to a parameterless method with a return value of the property type. Except as the target of an assignment, when a property is referenced in an expression, the get
accessor of the property is invoked to compute the value of the property (§7.1.1). The body of a get
accessor must conform to the rules for value-returning methods described in §10.5.7. In particular, all return
statements in the body of a get
accessor must specify an expression that is implicitly convertible to the property type. Furthermore, a get
accessor is required to terminate in a return
statement or a throw
statement, and control is not permitted to flow off the end of the get
accessor’s body.
A set
accessor corresponds to a method with a single value parameter of the property type and a void
return type. The implicit parameter of a set
accessor is always named value
. When a property is referenced as the target of an assignment, the set
accessor is invoked with an argument that provides the new value (§7.13.1). The body of a set
accessor must conform to the rules for void
methods described in §10.5.7. In particular, return
statements in the set
accessor body are not permitted to specify an expression.
Since a set
accessor implicitly has a parameter named value
, it is an error for a local variable declaration in a set
accessor to use that name.
Based on the presence or absence of the get
and set
accessors, a property is classified as follows:
get
accessor and a set
accessor is said to be a read-write property.get
accessor is said to be read-only property. It is an error for a read-only property to be the target of an assignment.set
accessor is said to be write-only property. Except as the target of an assignment, it is an error to reference a write-only property in an expression.In the example
public class Button: Control { private string caption; public string Caption { get { return caption; } set { if (caption != value) { caption = value; Repaint(); } } } public override void Paint(Graphics g, Rectangle r) { // Painting code goes here } }
the Button
control declares a public Caption
property. The get
accessor of the Caption
property returns the string stored in the private caption
field. The set
accessor checks if the new value is different from the current value, and if so, it stores the new value and repaints the control. Properties often follow the pattern shown above: The get
accessor simply returns a value stored in a private field, and the set
accessor modifies the private field and then performs any additional actions required to fully update the state of the object.
Given the Button
class above, the following is an example of use of the Caption
property:
Button okButton = new Button(); okButton.Caption = "OK"; // Invokes set accessor string s = okButton.Caption; // Invokes get accessor
Here, the set
accessor is invoked by assigning a value to the property, and the get
accessor is invoked by referencing the property in an expression.
The get
and set
accessors of a property are not distinct members, and it is not possible to declare the accessors of a property separately. The example
class A { private string name; public string Name { // Error, duplicate member name get { return name; } } public string Name { // Error, duplicate member name set { name = value; } } }
does not declare a single read-write property. Rather, it declares two properties with the same name, one read-only and one write-only. Since two members declared in the same class cannot have the same name, the example causes a compile-time error to occur.
When a derived class declares a property by the same name as an inherited property, the derived property hides the inherited property with respect to both reading and writing. In the example
class A { public int P { set {...} } } class B: A { new public int P { get {...} } }
the P
property in B
hides the P
property in A
with respect to both reading and writing. Thus, in the statements
B b = new B(); b.P = 1; // Error, B.P is read-only ((A)b).P = 1; // Ok, reference to A.P
the assignment to b.P
causes an error to be reported, since the read-only P
property in B
hides the write-only P
property in A
. Note, however, that a cast can be used to access the hidden P
property.
Unlike public fields, properties provide a separation between an object’s internal state and its public interface. Consider the example:
class Label { private int x, y; private string caption; public Label(int x, int y, string caption) { this.x = x; this.y = y; this.caption = caption; } public int X { get { return x; } } public int Y { get { return y; } } public Point Location { get { return new Point(x, y); } } public string Caption { get { return caption; } } }
Here, the Label
class uses two int
fields, x
and y
, to store its location. The location is publicly exposed both as an X
and a Y
property and as a Location
property of type Point
. If, in a future version of Label
, it becomes more convenient to store the location as a Point
internally, the change can be made without affecting the public interface of the class:
class Label { private Point location; private string caption; public Label(int x, int y, string caption) { this.location = new Point(x, y); this.caption = caption; } public int X { get { return location.x; } } public int Y { get { return location.y; } } public Point Location { get { return location; } } public string Caption { get { return caption; } } }
Had x
and y
instead been public
readonly
fields, it would have been impossible to make such a change to the Label
class.
Exposing state through properties is not necessarily any less efficient than exposing fields directly. In particular, when a property accessor is non-virtual and contains only a small amount of code, the execution environment may replace calls to accessors with the actual code of the accessors. This process is known as inlining, and it makes property access as efficient as field access, yet preserves the increased flexibility of properties.
Since invoking a get
accessor is conceptually equivalent to reading the value of a field, it is considered bad programming style for get
accessors to have observable side-effects. In the example
class Counter { private int next; public int Next { get { return next++; } } }
the value of the Next
property depends on the number of times the property has previously been accessed. Thus, accessing the property produces an observable side-effect, and the property should instead be implemented as a method.
The "no side-effects" convention for get
accessors doesn’t mean that get
accessors should always be written to simply return values stored in fields. Indeed, get
accessors often compute the value of a property by accessing multiple fields or invoking methods. However, a properly designed get
accessor performs no actions that cause observable changes in the state of the object.
Properties can be used to delay initialization of a resource until the moment it is first referenced. For example:
public class Console { private static TextReader reader; private static TextWriter writer; private static TextWriter error; public static TextReader In { get { if (reader == null) { reader = new StreamReader(File.OpenStandardInput()); } return reader; } } public static TextWriter Out { get { if (writer == null) { writer = new StreamWriter(File.OpenStandardOutput()); } return writer; } } public static TextWriter Error { get { if (error == null) { error = new StreamWriter(File.OpenStandardError()); } return error; } } }
The Console
class contains three properties, In
, Out
, and Error
, that represent the standard input, output, and error devices. By exposing these members as properties, the Console
class can delay their initialization until they are actually used. For example, upon first referencing the Out
property, as in
Console.Out.WriteLine("Hello world");
the underlying TextWriter
for the output device is created. But if the application makes no reference to the In
and Error
properties, then no objects are created for those devices.