Data Access, Properties, and Methods |
A complete PDF version of the text book is now available. The PDF version is an almost complete subset of the HTML version (where only a few, long program listings have been removed). See here. |
When a client of a class C accesses data in C via properties, the client of C may have the illusion that it accesses data directly. From a notational point of view, the client of C cannot tell the difference between access to a variable in C and access via a property.
Properties have not been invented in the process of creating C#. Properties have, in some forms, been used in Visual Basic and Delphi (which is a language in the Pascal family). Properties, in the sense discussed below, are not present in Java or C++. Java only allows data to be accessed directly or via methods. Therefore, in Java, it is always possible for clients of a class C to tell if data is accessed directly from a variable in C or indirectly via a method i C. In C#, it is not.
In this material we classify properties as operations, side by side with methods and similar abstractions. Underneath - in the Common Intermediate Language - properties are in fact treated as (getter and setter) methods.
|
18.1. Properties in C#
Contents Up Previous Next Slide Annotated slide Aggregated slides Subject index Program index Exercise index
When we use a property it looks like direct access of a variable. But it is not. A variable references to a stored location. A property activates a calculation which is encapsulated in an abstraction. The calculations that access data in a class C via properties should be efficient. If not, the clients of C are easily misled. Complicated, time consuming operations should be implemented in methods, see Chapter 20.
Let us first present a very simple, but at the same time a very typical example of properties. In Program 18.1 the Balance property accesses the private instance variable balance. Notice that the name of the property is capitalized, and that the name of the instance variable is not. This is a widespread convention in many coding styles.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | using System; public class BankAccount { private string owner; private decimal balance; public BankAccount(string owner, decimal balance) { this.owner = owner; this.balance = balance; } public decimal Balance { get {return balance;} } public void Deposit(decimal amount){ balance += amount; } public void Withdraw(decimal amount){ balance -= amount; } public override string ToString() { return owner + "'s account holds " + + balance + " kroner"; } } | |||
|
In class BankAccount it is natural to read the balance via a property, but is problematic to write the balance via a property. Therefore, there is no setter in the Balance property. Instead, Deposit and Withdraw operations (methods) are used. In general we should carefully consider the need for readability and writablity of individual instance variables.
The public Balance property as programmed in Program 18.1 provides for read-access to the private instance balance variable. You may complain that this is a complicated way of making the instance variable public. What is important, however, is that at a later point in the program evolution process we may change the private data representation. We may, for instance, eliminate the instance variable balance entirely, but keep the interface to clients - the Balance property - intact. This is illustrated in Program 18.2 below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | using System; public class BankAccount { private string owner; private decimal[] contributions; private int nextContribution; public BankAccount(string owner, decimal balance) { this.owner = owner; contributions = new decimal[100]; contributions[0] = balance; nextContribution = 1; } public decimal Balance { get {decimal result = 0; foreach(decimal ctr in contributions) result += ctr; return result; } } public void Deposit(Decimal amount){ contributions[nextContribution] = amount; nextContribution++; } public void Withdraw(Decimal amount){ contributions[nextContribution] = -amount; nextContribution++; } public override string ToString() { return owner + "'s account holds " + + Balance + " kroner"; } } | |||
|
The interesting thing to notice is that the balance of the bank account now is represented by the decimal array called contributions in line 6 of Program 18.2. The Balance property in line 16-22 accumulates the contributions in order to calculate the balance of the account.
From a client point of view we can still read the balance of the bank account via the Balance property. Underneath, however, the implementation of the Balance getter in line 16-22 of Program 18.2 has changed a lot compared to line 14 of Program 18.1. We show a simple client program in Program 18.3, and its output in Listing 18.4 (only on web).
The client program in Program 18.3 can both be used together with BankAccount in Program 18.1 and Program 18.2. Thus, the client program has no awareness of the different representation of the balance in the two versions of class BankAccount. The only thing that matters in the relation between class BankAccount and its client class is the client interface of class BankAccount.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | using System; class C{ public static void Main(){ BankAccount ba = new BankAccount("Peter", 1000); Console.WriteLine(ba); ba.Deposit(100); Console.WriteLine("Balance: {0}", ba.Balance); ba.Withdraw(300); Console.WriteLine("Balance: {0}", ba.Balance); ba.Deposit(100); Console.WriteLine("Balance: {0}", ba.Balance); } } | |||
|
1 2 3 | Balance: 1100 Balance: 800 Balance: 900 | |||
|
Above we have discussed getting of instance variables in objects. In the BankAccount class we have seen how to access to the balance instance variable via a getter in the property Balance. Technically, it is also possible to change the value of the balance instance variable by a setter. Conceptually, we would rather prefer to update the bank accounts by use of the methods Deposit and WithDraw. Nevertheless, here is the Balance property with both a getter and a setter.
public decimal Balance { get {return balance;} set {balance = value;} }
The setter is activated in an assignment like b.Balance = expression; The important thing to notice is that the property Balance is located at the left-hand side of the assignment operator. The value of expression is bound to the pseudo variable value in the property, and as it appears in the setter, the value of value is assigned to the instance variable balance.
Properties can also be used for getting and setting fields of struct values. In addition, properties can be used to get and set static variables in both classes and structs.
This ends the essential examples of the Balance property of class BankAccount. In this web edition we show an additional exotic variation of the Balance property in class BankAccount, see Program 18.5 (only on web). If you do not care, you can safely proceed to Section 18.2. If you do care, you should first notice that we use the boolean variable readMode to enforce a strict alternation between getting and setting the balance of the bank account. First reading (getting) is allowed. Next writing (setting) has to take place. Next reading (getting) must take place, etc. The interesting thing is to notice in this context is that properties allow us to program such enforced alternation of getting and setting.
Alternated getting and setting may seem strange at first sight. And - I should admit - it is probably not very useful in the banking world. The idea is, however, always to access and update the bank account ba in the following way:
ba.Balance = ba.Balance + 100; // deposit the amount of 100 ba.Balance = ba.Balance - 400; // withdraw the amount of 100
This can also be written in the following shorter way:
ba.Balance += 100; // deposit the amount of 100 ba.Balance -= 400; // withdraw the amount of 100
In relation to this example it is crucial to understand that ba.Balance += 100; involves exactly one reading and one writing via the Balance property, in this order. The sample client program in Program 18.6 (only on web) and its output in Listing 18.7 (only on web) gives additional insight.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | using System; public class BankAccount { private string owner; private decimal balance; private bool readMode; public BankAccount(string owner, decimal balance) { this.owner = owner; this.balance = balance; this.readMode = true; } public decimal Balance { get {if (readMode){ readMode = false; return balance; } else throw new Exception("Cannot read now!"); } set {if (!readMode){ balance = value; readMode = true; } else throw new Exception("Cannot write now!"); } } public override string ToString() { return owner + "'s account holds " + + balance + " kroner"; } } | |||
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | using System; class C{ public static void Main(){ BankAccount ba = new BankAccount("Peter", 1000); Console.WriteLine(ba); ba.Balance += 100; // read balance + deposit Console.WriteLine(ba); ba.Balance -= 300; // read balance + withdraw Console.WriteLine(ba); decimal amount = ba.Balance; // read balance ba.Balance = amount + 100; // deposit Console.WriteLine(ba); ba.Balance = 400; // illegal deposit Console.WriteLine(ba); } } | |||
|
1 2 3 4 5 6 7 8 | Peter's account holds 1000 kroner Peter's account holds 1100 kroner Peter's account holds 800 kroner Peter's account holds 900 kroner Unhandled Exception: System.Exception: Cannot write now! at BankAccount.set_Balance(Decimal value) at C.Main() | |||
|
This ends our discussion of the exotic variation of class BankAccount.
Exercise 5.1. A funny BankAccount In this exercises we provide a version of class BankAccount with a "funny version" of the Balance property. You should access the exercise via the web version, in order to get access to the source programs involved. Study the Balance property of the funny version of class BankAccount. Explain the behaviour of the given client of the funny BankAccount. Next test-run the program and confirm your understanding of the two classes. Please notice how difficult it is to follow the details when properties - like Balance in the given version of class BankAccount - do not pass data directly to and from the instance variables. Solution |
18.2. Properties: Class Point with polar coordinates
Contents Up Previous Next Slide Annotated slide Aggregated slides Subject index Program index Exercise index
We will now look at another very realistic example of properties in C#.
The example in Program 18.8 is a continuation of the Point examples in Program 11.2. Originally, and for illustrative purposes, in Program 11.2 we programmed a simple point with public access to its x and y coordinates. We never do that again. The x and y coordinates used in Program 18.8 are called rectangular coordinates because they delineate a rectangle between the point and (0,0).
In the class Point in Program 18.8 we have changed the data representation to polar coordinates. In the paper version of the material we show only selected parts of the class. (We have, in particular, eliminated a set of static methods that convert between rectangular and polar coordinates). In the web version the full class definition is included. Using polar coordinates, a point is represented as a radius and an angle. In addition, and as emphasized with purple in Program 18.8 we have programmed four properties, which access the polar and the rectangular coordinates of a point. The properties Angle and Radius are, of course, trivial, because they just access the underlying private instance variables. The properties X and Y require some coordinate transformations. We have programmed all the necessary coordinate transformations in static private methods of class Point. In the web edition of the material these methods are shown at the bottom of class Point.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 | // A versatile version of class Point with Rotation and internal methods // for rectangular and polar coordinates. using System; public class Point { public enum PointRepresentation {Polar, Rectangular} private double r, a; public Point(double x, double y){ r = RadiusGivenXy(x,y); a = AngleGivenXy(x,y); } public Point(double par1, double par2, PointRepresentation pr){ if (pr == PointRepresentation.Polar){ r = par1; a = par2; } else { r = RadiusGivenXy(par1,par2); a = AngleGivenXy(par1,par2); } } public double X { get {return XGivenRadiusAngle(r,a);} } public double Y { get {return YGivenRadiusAngle(r,a);} } public double Radius { get {return r;} } public double Angle{ get {return a;} } public void Move(double dx, double dy){ double x, y; x = XGivenRadiusAngle(r,a); y = YGivenRadiusAngle(r,a); r = RadiusGivenXy(x+dx, y+dy); a = AngleGivenXy(x+dx, y+dy); } public void Rotate(double angle){ a += angle; } public override string ToString(){ return "(" + XGivenRadiusAngle(r,a) + "," + YGivenRadiusAngle(r,a) + ")"; } private static double RadiusGivenXy(double x, double y){ return Math.Sqrt(x * x + y * y); } private static double AngleGivenXy(double x, double y){ return Math.Atan2(y,x); } private static double XGivenRadiusAngle(double r, double a){ return r * Math.Cos(a); } private static double YGivenRadiusAngle(double r, double a){ return r * Math.Sin(a); } } | |||
|
Exercise 5.2. Point setters In the Point class on the accompanying slide we have shown how to program getter properties in the class Point. Extend the four properties with setters as well. The new version of this class will support mutable points. Write a small client program that demonstrates the use of the setter properties. Hint: Please be aware that the revised class should allow us to get and set the rectangular and polar coordinates (x, y, angle, radius) of a point independent of each other. You should first consider what it means to do so. Solution |
18.3. Automatic Properties
Contents Up Previous Next Slide Annotated slide Aggregated slides Subject index Program index Exercise index
Many of the properties that we write are trivial in the sense that they just get or set a single instance variable. It is tedious to write such trivial properties. It may be possible to ask the programming environment to help with creation of trivial properties. Alternatively in C# 3.0, it is possible for the compiler to generate the trivial properties automatically from short descriptions.
Let us study an example, which extends the initial bank account example from Program 18.1. The example is shown in Program 18.9 and the translation done by the compiler is shown in Program 18.10.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | using System; public class BankAccount{ // automatic generation of private instance variables public BankAccount(string owner, decimal balance){ this.Owner = owner; this.Balance = balance; } public string Owner {get; set;} public decimal Balance {get; set;} public override string ToString(){ return Owner + "'s account holds " + Balance + " kroner"; } } | |||
|
Based on the formulations in line 12 and 14, the compiler generates the "real properties" shown below in line 13-21 of Program 18.10.
As an additional and important observation, it is no longer necessary to define the private instance variables. The compiler generates the private "backing" instance variables automatically. In terms of the example, the lines 5-6 in Program 18.10 are generated automatically.
As a consequence of automatically generated instance variables, the instance variables cannot be accessed in the class. The names of the instance variables are unknown, and therefore they cannot be used at all! Instead, the programmer of the class accesses the hidden instance variables through the properties. As an example, the owner and balance are accessed via the properties Owner and Balance in line 17 of the ToString method in Program 18.9.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | using System; public class BankAccount{ private string _owner; private decimal _balance; public BankAccount(string owner, decimal balance){ _owner = owner; _balance = balance; } public string Owner { get {return _owner;} set {_owner = value;} } public decimal Balance { get {return _balance;} set {_balance = value;} } public override string ToString(){ return Owner + "'s account holds " + Balance + " kroner"; } } | |||
|
Because properties, internally in a class, play the role of instance variables, it is hardly meaningful to have an automatic property with a getter and no setter. The reason is that the underlying instance variable cannot be initialized - simple because its name is not available. Similarly, an automatic property with a setter, but not getter, would not make sense, because we would not be able to access the underlying instance variable. Therefore the compiler enforces that an automatic property has both a getter and a setter. It is possible, however, to limit the visibility of either the getter or setter (but not both). As an example, the following definition of the Balance property
public string Balance {get; private set;}
provides for public reading, but only writing from within the class. In class BankAccount, this is probably what we want. It is OK to access the balance from outside, but we the account to be updated by use of either the Deposit method or the Withdraw method.
Finally, let us observe that the syntax of automatic properties is similar to the syntax used for abstract properties, see Section 30.3.
I recommend that you use automatic properties with some care! It is worth emphasizing that a class almost always encapsulates some state - instance variables - some of which can be accessed by properties. It feels strange that we can program in a way - with automatic properties - without ever declaring the instance variables explicitly. And it feels strange that we never, from within the class, refers to the instance variables. Automatic properties are useful in the early life of a class, where we allows for direct (one-to-one) access to the instance variables via methods or properties. In an early version of the class Point of Section 11.6, this was the case. (We actually started with public instance variables, but this mistake is taken care of in Exercise 3.3). Later on, we may internally invent a more sophisticated data representation - or we may change our mind with respect to the data representation. In the Point class we went from a rectangular representation to a polar representation. The data representation details should always be a private and internal concern of the class. When this change happens we have to introduce instance variables, exactly as described in Section 11.8. When doing so we have to get rid of the automatic properties. The introduction of instance variables and the substitution of the automatic properties with 'real properties' represent internal changes to the Point class. The clients of Point will not be affected by these changes. This is the point! In the rest of the lifetime of class Point, it is unlikely that automatic properties will be 'part of the story'. |
Automatic properties contribute to a C# 3.0 convenience layer on top of
already existing means of expressions.
|
18.4. Object Initialization via Properties
Contents Up Previous Next Slide Annotated slide Aggregated slides Subject index Program index Exercise index
In this section we will see that property setters in C#3.0 can be used for initialization purposes in the slipstream of constructors. The properties that we use for such purposes can be automatic properties, as discussed in Section 18.3.
Let us again look at a simple BankAccount class, see Program 18.11. The class has two automatic properties, backed by two instance variables, which we cannot access. In addition, the class has two constructors, cf. Section 12.4.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | using System; public class BankAccount{ // automatic generation of private instance variables public BankAccount(): this("NN") { } public BankAccount(string owner){ this.Balance = 0; this.Owner = owner; } public string Owner {get; set;} public decimal Balance {get; set;} public override string ToString(){ return Owner + "'s account holds " + Balance + " kroner"; } } | |||
|
Below, in Program 18.12 we make an instance of class BankAccount in line 6. As emphasized in purple the owner and balance is initialized by a so-called object initializer, in curly brackets, right after new BankAccount. The initializer refers the setter of the automatic properties Owner and Balance. All together we have made a new bank account by use of the parameterless constructor. The initialization is done by the setters of the Owner and the Balance properties.
In line 7 of Program 18.12 we make another instance of class BankAccount, where Owner is initialized via the actual parameter "Bill" passed to the constructor, and the balance is initialized via an object initializer in curly brackets.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | using System; public class Client { public static void Main(){ BankAccount ba1 = new BankAccount{Owner = "James", Balance = 250}, ba2 = new BankAccount("Bill"){Balance = 1200}; Console.WriteLine(ba1); Console.WriteLine(ba2); } } | |||
|
The compiler translates line 6 of Program 18.12 to line 6-8 in Program 18.13. Similarly, line 7 of Program 18.12 is translated to line 10-11 in Program 18.13.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | using System; public class Client { public static void Main(){ BankAccount ba1 = new BankAccount(); ba1.Owner = "James"; ba1.Balance = 250; BankAccount ba2 = new BankAccount("Bill"); ba2.Balance = 1200; Console.WriteLine(ba1); Console.WriteLine(ba2); } } | |||
|
Let us, finally, elaborate the example with the purpose of demonstrating nested object initializers. In Program 18.14 we show the classes BankAccount and Person. As can be seen, an instance of class BankAccount refers to an owner which is an instance of class Person. It turns out to be crucial for the example that the Owner of a BankAccount refers to a Person object (more specifically, that it is not a null reference).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | using System; public class BankAccount{ public BankAccount(){ this.Balance = 0; this.Owner = new Person(); } public Person Owner {get; set;} public decimal Balance {get; set;} public override string ToString(){ return Owner + "'s account holds " + Balance + " kroner"; } } public class Person { public string FirstName {get; set;} public string LastName {get; set;} public override string ToString(){ return FirstName + " " + LastName; } } | |||
|
In the Client class, see Program 18.15 we make two BankAccounts , ba1 and ba2. The initializers, emphasized in line 7-9 and 12-14, initialize the Owner of a BankAccount with nested object initializers. Notice that there is no new operator in front of {FirstName = ..., LastName = ...}. The Person object already exists. In this example, it is instantiated in line 7 of Program 18.14 together with the BankAccount.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | using System; public class Client { public static void Main(){ BankAccount ba1 = new BankAccount{ Owner = {FirstName = "James", LastName = "Madsen"}, Balance = 250}, ba2 = new BankAccount{ Owner = {FirstName = "Bill", LastName = "Jensen"}, Balance = 500}; Console.WriteLine(ba1); Console.WriteLine(ba2); } } | |||
|
The use of property names (setters) in object initializers gives the effect of keyword parameters. Keyword parameters refer to a formal parameter name in the actual parameter list. Keyword parameters can be given in any order, as a contrast to positional parameters, which must given in the order dictated by the formal parameter list. In addition, the caller of an abstraction that accepts keyword parameters can choose not to pass certain keyword parameters. In that case, defaults will apply. In case of C#, such default values can be defined within the body of the constructors.
18.5. Summary of properties in C#
Contents Up Previous Next Slide Annotated slide Aggregated slides Subject index Program index Exercise index
The syntax of property declarations is shown in Syntax 18.1. Both the get part and the set part are optional. Syntactically, the signature a property declaration is like a method declaration without formal parameters. As can be seen, the syntax of a property body is very different from the syntax of a method body.
| |||
|
The following summarizes the characteristics of properties in C#:
|
The following observations about property naming reflect a given coding style. A coding style is not enforced by the compiler.
A C# property will often have the same name as a private data member The name of the property is capitalized - the name of the data member is not |