Deep C# - Structs & Classes
Written by Mike James   
Monday, 08 November 2021
Article Index
Deep C# - Structs & Classes
Example
Simple Types

However, let’s look at this more closely because a struct and a class look much more alike than say an int and a class and mistakes and misunderstandings are easy to make. The most immediate impact of this difference is that you don’t have to use new when creating an instance of a struct. That is, you can create an instance of PointV using:

PointV a;

and this immediately creates a PointV object which you can use:

a.x = 10

The similar class, however, needs new to create an instance and:

PointR b;

creates only a reference variable. To make use of a PointR object you also have to use:

b = new PointR();
b.x = 20;

To make the difference even clearer, you can create other references to the same PointR object as in:

PointR c;
c = b;

Now c and b refer to the same PointR object and the same x value is changed by c.x = 30 or b.x = 30. In the case of a struct, and a PointV in particular, you cannot create multiple references to it and assignment creates a copy of the struct. That is:

PointV d;
d = a;

makes an independent copy of the struct a. Now assigning to d.x changes a different x to that assigned in a.x.

If you think you follow, try predicting the values in each of the fields of this admittedly complicated set of assignments:

PointV a;
a.x = 1;
a.y = 2

PointR b = new PointR();
b.x = 3;
b.y = 4;

PointV c;
PointR d;

c = a;
d = b;
c.x = 5;
d.x = 6;

The answer is:

a.x = 1,a.y = 2
b.x = 6,b.y = 4
c.x = 5,c.y = 2
d.x = 6,d.y = 4

because d is the same object as b, but c is a new object initialized to be the same as a.

The difference between the two types of behavior is usually expressed as value semantics versus reference semantics, i.e. is the variable the value or a reference to the value. The advice is that structs should be used for simple data types where value assignment seems natural and classes should be used for more advanced types where reference assignment is natural.

If you want to use a class to store data then C# has a predefined record class that makes it particularly easy, but a record is still a class and reference semantics apply.

Arrays

Value semantics also has another spin-off when it comes to arrays and indeed other complex data types. When you create an array you always use something closer to reference-type semantics as in:

int[] a = new int[10];

This creates 10 ints in a contiguous block of memory and this is very efficient. Now consider:

PointR[] b = new PointR[10];

This creates a contiguous block of 10 reference variables of type PointR all referencing nothing at all. That is, all you have created is an array of references to null

To finish the array construction you have to use something like:

for(int i = 0;i<b.length;i++){
  b[i] = new PointR();
}

This, of course, is not as efficient because the 10 objects are created on the heap and have to be memory managed, but all object-oriented languages suffer from this type of problem. The good news is that C# treats arrays created using structs in the same way as simple types, i.e. using value semantics. For example:

PointV[] c = new PointV[10];

immediately creates an array of 10 PointV objects in a contiguous block of memory.

As long as you keep thinking of value types and reference types as being more or less the same, except that reference types don't automatically create the object they reference, you should follow and things should seem simple. 

Structs versus Classes

Structs are so important and versatile that, before moving on, it is worth saying a few words that compares and contrasts them to classes while we are on the topic.

As you might expect of a value type, structs don’t support inheritance – they inherit from objects, but that’s the end of the inheritance hierarchy. They do support interfaces, however, more of which in Chapter 6. Also a struct can have methods, properties and constructors, but it can’t have a destructor. Its default, i.e. parameter-less, constructor can’t be changed and always initializes all its fields to the default value of 0 for a value type and to null for a reference type. You can add your own explicit constructor to initialize fields to specific values. Surprisingly, although you can create a struct without the use of new, you can write:

PointV a = new PointV();

It is important to realize that this way of creating a struct is no different from the point of view of value or reference semantics - a struct is always a value type.

There is a difference, however, and it is that the use of new calls the struct’s default constructor which sets each field of the struct to its default value. If you don’t use new then the fields are treated as undefined and the compiler displays an error message if you try to make use of any of the fields before they have been assigned to.



Last Updated ( Monday, 08 November 2021 )