Theme index -- Keyboard shortcut: 'u'  Previous theme in this lecture -- Keyboard shortcut: 'p'  Next slide in this lecture -- Keyboard shortcut: 'n'Reference types, Value types, and Patterns

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.

13.  Reference Types

Objects are accessed via references. When we create an object - by class instantiation - we obtain a reference to the new object. If we send a message to the object it is done via the reference. If the object is passed as parameter it is done via the reference. And when the object is returned from a method it is the reference to the object which is returned. We sometimes use the word reference semantics for all of this. Reference semantics should be seen as a contrast to value semantics. Value semantics is discussed in Chapter 14.

13.1 Reference Types13.4 Comparing and copying objects via references
13.2 Illustration of variables of reference types13.5 Equality in C#
13.3 Overview of reference types in C#
 

13.1.  Reference Types
Contents   Up Previous Next   Slide Annotated slide Aggregated slides    Subject index Program index Exercise index 

A class is a reference type

Objects instantiated from classes are accessed by references

The objects are allocated on the heap

Instances of classes are dealt with by use of so-called reference semantics

Although we state that references (in C# and similar languages) correspond to pointers in C, we should be a little careful to equivalize these. In the ordinary (safe part) of C# there is no such thing as reference arithmetic, along the lines of pointer arithmetic in C. There is no address operator, and there is no dereferencing. (In an unsafe part of C# it is possible to work with pointers like in C, but we will not care about this part of the C#). References are automatically dereferenced, when it is appropriate to do so. If r is a reference, the expression r.p is used to access the property p in the object referenced by r. But the expressions *r and r->p are both illegal.

  • Reference semantics:

    • Assignment, parameter passing, and return manipulates references to objects

  • The heap:

    • The memory area where instances of classes are allocated

    • Allocation takes place when a class is instantiated

    • Deallocation takes place when the object no longer affects the program

      • In practice, when there are no references left to the object

      • By a separate thread of control called the garbage collector

 

13.2.  Illustration of variables of reference types
Contents   Up Previous Next   Slide Annotated slide Aggregated slides    Subject index Program index Exercise index 

Let us now illustrate how assignments work on references. The situation shown in Figure 13.1 depicts the variables p1 and p2 just before we execute the assignment p1 = p2. The situation in the figure is established by line 1 and 2 of Program 13.1. Notice that the variables each contain a reference to a Point object. The variables do not contain the object themselves, but instead references to the points.

Figure 13.1    Variables of reference types. The situation before the assignment p1 = p2.

1
2
3
4
Point p1 = new Point(1.0, 2.0),
      p2 = new Point(3.0, 4.0);

p1 = p2;
Program 13.1    The variables p1 and p2 refer to two points.

Following the assignment p1 = p2 in line 3 of Program 13.1 both p1 and p2 reference the same Point object. Thus, the situation is as depicted in Figure 13.2. The Point (1.0, 2.0) is now inaccessible (unless referenced from other variables) and the point will disappear automatically upon the next turn of the garbage collector.

Figure 13.2    Variables of reference types. The situation after the assignment p1 = p2.

With the knowledge from this section you are encouraged to review the discussion of Program 12.1 in Section 12.2.

 

13.3.  Overview of reference types in C#
Contents   Up Previous Next   Slide Annotated slide Aggregated slides    Subject index Program index Exercise index 

Classes are reference types in C#, but there are others as well

It is reasonable to ask which types in C# act as reference types, and which do not. Below we list the reference types in C#:

  • Classes

    • Strings

    • Arrays

  • Interfaces

    • Similar to classes. Contain no data. Have only signatures of methods

  • Delegates

    • Delegate objects can contain one or more methods

    • Used when we deal with methods as data

We encounter interfaces in Chapter 31. Interface types contain references, and variables of interface types behave in the same way as in the example shown in Section 13.2.

A delegate is a new type, the values of which are accessed as references. We introduce delegates in Chapter 22.

Both strings and arrays are well-known, and we are used to accessing these via pointers in C. In C# - as well - arrays and strings are accessed via references.

 

13.4.  Comparing and copying objects via references
Contents   Up Previous Next   Slide Annotated slide Aggregated slides    Subject index Program index Exercise index 

There are several questions that can be asked about comparing and copying objects that are accessed by references. We list some below, and we will attempt to answer the questions in the remaining parts of this section.

Do we compare references or the referenced objects?

Do we copy the reference or the referenced object?

How deep do we copy objects that reference other objects?

Let us assume, like in Program 13.1, that p1 and p2 are references to Points, where the type Point is defined by a class. Then the expression p1 == p2 returns if p1 and p2 reference the same point. That p1 and p2 reference the same point means that the two involved objects are created by the same activation of new(...). In many context, we say that p1 and p2 are identical. (Identical objects and object identity is discussed in Section 11.14). Relative to Figure 13.1 the value of p1 == p2 is false. Relative to Figure 13.2 the value of p1 == p2 is true. The expression p1 == p2 compares the locations (addresses) to which p1 and p2 refer. The expression does not compare the instance variables of the points referred to by p1 and p2.

In the same way, the assignment p1 = p2 manipulates only the references. We have already seen that in Section 13.2. The assignment p1 = p2 does not, in any way, copy the object referenced by p2.

Above, we have explained reference comparison and assignment. It makes sense to have shallow and deep variations of these. This can be summarized as follows:

  • Comparing

    • Reference comparison

      • Compares if two references refers to objects created by the same execution of new

    • Shallow and deep comparison

      • Pair-wise comparison of fields - at varying levels

  • Copying:

    • Reference copying

    • Shallow or deep copying

      • Is also known as cloning

      • Somehow supported by the method MemberwiseClone in System.Object

Even in the case where p1 == p2 is false (i.e., p1 and p2 are not identical) it makes sense to claim that p1 and p2 are equal in some sense. It may, for instance, be the case that all instance variables are pair-wise equal. But what does it mean for the instance variables to be pair-wise equal? In case the instance variables are references we are back to the original question, and we can therefore apply recursion in our reasoning about equality. We talk about shallow equality if we apply (fall back to) reference equality at the second level. If we do not apply reference equality at any level we talk about deep equality. If p1 and p2 are deep equal the graph structures they reference are structural identical (isomorphic).

If p1 and p2 are reference equal they are also shallow equal. And if p1 and p2 are shallow equal they are also deep equal. The inverse propositions are not necessarily true, of course.

If you got the idea of the different kinds of comparison, you can immediately use this insight for copying as well. Let us describe this very briefly. The assignment p1 = p2 just copies one reference. We may ask for a shallow copy of p2 by copying value fields and by assigning corresponding reference fields to each other. And we may ask for a deep copy by not using reference copying at any level. (This is not exactly true if there is more than one reference to a given object - please consider!)

In case you need shallow or deep copying you should program such operations yourself. In general, various kinds of copying depend deeply on the type of the object. C# supports a shallow clone operation, but you must explicitly 'enable it'. How this is done is discussed in Section 32.7.

An assignment of the form var = obj1 copies a reference

A comparison of the form obj1 == obj2 compares references (unless overloaded)

 

13.5.  Equality in C#
Contents   Up Previous Next   Slide Annotated slide Aggregated slides    Subject index Program index Exercise index 

In this section we review the different equality operations in C#. All methods mentioned in this section belong the class Object, see Section 28.3. We only care about reference types in this section, because the enclosing chapter is about reference types. Equality among 'objects' that belong to value types is an different story.

  • o1.Equals(o2)     - equality

    • By default, true if o1 and o2 are created by execution of the same new

    • Can be redefined in a particular class

  • Object.ReferenceEquals(o1, o2)     - identity

    • True if both o1 and o2 are null, or if they are created by execution of the same new

    • Static - cannot be redefined.

  • Object.Equals(o1, o2)

    • True if Object.ReferenceEquals(o1, o2), or if o1.Equals(o2)

  • o1 == o2

    • True if both o1 and o2 are null, or if they are created by execution of the same new

    • An overloadable operator

Notice that in case we need a type-dependent comparison we redefine the Equals (in the first item above). Equals is typically redefined if we wish to implement a value-like comparison of two objects, as opposed to the default reference comparison. (In a value-like comparison we compare pairs of fields from the two objects). It is not easy to redefine Equals correctly. We discuss how it should be done in Section 28.16.

ReferenceEquals is a static method. It must therefore be activated by the form Object.ReferenceEquals(o1, o2). If you, for some reason, redefine the == operator as well as the Equals instance method - both of which per default are reference equality operations - the static ReferenceEquals comes in handy if you need to compare references to objects. Alternatively, you will have to cast one of operands to type Object before you use ==.

The static Equals method is primarily justified because it allows one or both of the parameters o1 or o2 to be null. In the non-static Equals methods, it will cause an exception if o1 is null. Notice that redefinition of the Equals instance method affects the static Equals method.

In C# it is allowed to overload the == operator. Typically, == is overloaded to obtain some kind of shallow comparison, see Section 13.4. If the == operator is overloaded you should also redefine the Equals method, such that o1 == o2 and o1.Equals(o2) have the same value (whenever o1 is not null).

It is worth pointing out that the meaning of o1 == o2 is resolved staticially, because operator overloading (see Chapter 21) is a static issue in C#. In contrast, o1.Equals(o2) is resolved dynamically, because the instance method Equals is a virtual method (see Section 28.14) in class Object. This affects both flexibility (where Equals is the winner) and efficiency (where == is the winner). Exercise 4.1 is related to theses observations.

It is worthwhile and recommended to read about equality in the C# documentation of Equals in the System namespace. Let us also point out that there exists a couple of interfaces that involve equality, most directly IEquality (see Section 42.9 ), but indirectly also Icomparable (see Section 42.8).

Redefinition of equality operators and methods: Recommendations.

FOCUS BOX 13.1

As above, we assume that we deal with reference types. If you do not redefine the Equals instance method nor the == operator, both of them denote reference equality.

If it is natural and important that equality between objects of your class should rely on the data contents (instance variables) of your class, rather than the referenced locations of the involved objects, you should redefine the Equals instance method. Follow the guidelines in Section 28.16. As part of this, remember that equality should be reflexive (x.Equals(x)), symmetric (x.Equals(y) implies that y.Equals(x)), and transitive (x.Equals(y) and y.Equals(z) implies that x.Equals(z)).

In general, you are not recommended to overload (redefine) the == operator. Most programmers with a C background will be surprised if x == y (for references or pointers) does not compare the references in x and y. If you overload the Equals instance method, you most likely do not want to touch the == operator. Thus, == will remain as the reference equality operator.

If - against these recommendations - you overload the == operator, you should make sure that the meaning (semantics) of == and Equals are the same. This can, for instance, be obtained by implementing Equals by means of ==.

It would be tremendously confusing to have two different meanings of == and Equals, both of which differ from the meaning of ReferenceEquals.


Exercise 4.1. Equality of value types and reference types

Take a close look at the following program which uses the == operator on integers.

using System;

class WorderingAboutEquality{

  public static void Main(){
    int i = 5,
        j = 5;

    object m = 5,
           n = 5;  

    Console.WriteLine(i == j);
    Console.WriteLine(m == n);
  }

}

Predict the result of the program. Compile and run the program, and explain the results. Were your predictions correct?

Solution

Generated: Monday February 7, 2011, 12:14:40
Theme index -- Keyboard shortcut: 'u'  Previous theme in this lecture -- Keyboard shortcut: 'p'  Next slide in this lecture -- Keyboard shortcut: 'n'