Structs

 
Chapter 3 - Object-Oriented C#
bySimon Robinsonet al.
Wrox Press 2002
  

So far, we have seen how classes can be great way of encapsulating objects in your program. We have also seen how they are stored on the heap in a way that gives you much more flexibility in data lifetime, but with a slight cost in performance. This performance cost is small thanks to the optimizations of managed heaps. However, there are some situations when all you really need is a small data structure. In this case, a class provides more functionality than you need, and for performance reasons you probably don't want the performance overhead of using the managed heap. Look at this example:

   class Dimensions     {     public double Length;     public double Width;     }   

We've defined a class called Dimensions , which simply stores the length and width of some item. Perhaps we're writing a furniture-arranging program to let people experiment with rearranging their furniture on the computer and we want to store the dimensions of each item of furniture. It looks like we're breaking the rules of good program design by making the fields public, but the point is that we don't really need all the facilities of a class for this at all. All we have is two numbers , which we find convenient to treat as a pair rather than individually. There is no need for lots of methods , or for us to be able to inherit from the class, and we certainly don't want to have the .NET runtime go to the trouble of bringing in the heap with all the performance implications, just to store two double s.

Instead, we're better off defining Dimensions as something called a struct . To do this, the only thing we need to change in the code is to replace the keyword class with struct :

   struct Dimensions   {       public double Length;       public double Width;    } 

You can think of structs in C# as being like scaled down classes. They are basically the same as classes, but designed more for cases where you simply want to group some data together. They differ from classes in the following ways:

  • Structs are value types, not reference types. This means they are stored either in the stack or inline (if they are part of another object that is stored on the heap), and have the same lifetime restrictions as the simple data types.

  • Structs do not support inheritance.

  • There are some differences in the way constructors work for structs. In particular, the compiler always supplies a default no-parameter constructor, which you are not permitted to replace.

  • With a struct, you can (if you wish) specify how the fields are to be laid out in memory.

Also, because structs are really intended to group data items together, you'll sometimes find that most or all of their fields are declared as public . This is strictly speaking contrary to the guidelines for writing .NET code - according to Microsoft, fields (other than const fields) should always be private and wrapped by public properties. However, for simple structs, many developers would nevertheless consider public fields to be acceptable programming practice.

Over the following pages, we'll go through the differences between classes and structs in more detail. We won't, however, look at the facility to specify how the fields in a struct are laid out in memory. In C# this feature can be regarded as fairly esoteric; it is only rarely needed (usually for calling native API functions), so we are treating it as beyond the scope of this book. If you do need to do this, you should look up the StructLayout attribute in the MSDN documentation.

Structs Are Value Types

Although structs are value types, you can often treat them syntactically in the same way as classes. For example, with our definition of Dimensions above, we could write:

   Dimensions point = new Dimensions();     point.Length = 3;     point.Width = 6;   

Note that because structs are value types, the new operator does not work in the same way as it does for classes and other reference types. Instead of allocating memory on the heap, the new operator simply calls the appropriate constructor, according to the parameters passed to it, initializing all fields. Indeed, for structs it is perfectly legal to write:

   Dimensions point;     point.Length = 3;     point.Width = 6;   

If Dimensions was a class, this would produce a compilation error, because point would contain an uninitialized reference - an address that points nowhere, so we could not start setting values to its fields. For a struct however, the variable declaration actually allocates space on the stack for the entire struct, so it's ready to assign values to. Note, however, that the following code would cause a compilation error, with the compiler complaining that you are using an uninitialized variable:

   Dimensions point;     Double D = point.Length;   

Structs follow the same rules as any other data type: everything must be initialized before use. A struct is considered fully initialized either when the new operator has been called against it, or when values have been individually assigned to all its fields. And of course, a struct defined as a member field of a class is initialized by being zeroed-out automatically when the containing object is initialized.

The fact that structs are value types will affect performance, though depending on how you use your struct, this can be good or bad. On the positive side, allocating memory for structs is very fast because this takes place inline or on the stack. The same goes for removing structs when they go out of scope. On the other hand, whenever you pass a struct as a parameter or assign a struct to another struct (as in A=B , where A and B are structs), the full contents of the struct are copied, whereas for a class only the reference is copied . This will result in a performance loss that depends on the size of the struct - this should emphasize the fact that structs are really intended for small data structures. Note, however, that when passing a struct as a parameter to a method, you can avoid this performance loss by passing it as a ref parameter - in this case only the address in memory of the struct will be passed in, which is just as fast as passing in a class. On the other hand, if you do this, you'll have to be aware that it means the called method can in principle change the value of the struct.

Structs and Inheritance

Structs are not designed for inheritance. This means that it is not possible to inherit from a struct, and a struct cannot derive from any class. The only exception to this is that structs, in common with every other type in C#, derive ultimately from the class System.Object , and it is even possible to override them in structs - an obvious example would be overriding the ToString() method.

For structs, the derivation from System.Object . ValueType adds no new methods of its own, but provides overridden implementations of some of the Object methods that are more appropriate to value types.

Defining methods for structs is exactly the same as defining them for classes:

 struct Dimensions {    public double Length;    public double Width; 
 Dimensions(double length, double width)     { Length=length; Width=width; }   public override string ToString()     {     return "( " + Length.ToString() + " , " + Width.ToString() + " )";     }   

We declare the method exactly as we would for a class. Note, however, that it is not possible to declare any member of a struct as virtual , abstract, or sealed . To do so would imply that we were intending other classes to inherit from struct, but structs will not allow you to do that.

Constructors for Structs

You can define constructors for structs in exactly the same way that you can for classes, except that you are not permitted to define a constructor that takes no parameters. This may seem nonsensical , and the reason is buried in the implementation of the .NET runtime. There are some rare circumstances in which the .NET runtime would not be able to call a custom zero-parameter constructor that you have supplied. Microsoft has therefore taken the easy way out and banned zero-parameter constructors for structs in C#. This feature seems to be one of the few poor aspects of the design of .NET, and has caused some controversy in .NET- related newsgroups.

Having said that, the default constructor, which initializes all fields to zero values, is always present implicitly, even if you supply other constructors that take parameters. It's also not possible to sneakily work round the default constructor by explicitly supplying initial values for fields. The following code will cause a compile-time error:

   struct Dimensions     {     public double Length = 1;       // error. Initial values not allowed     public double Width = 2;        // error. Initial values not allowed   

Of course, if Dimensions had been declared as a class, this code would have compiled without any problems.

Incidentally, you can supply a Close() or Dispose() method for a struct in the same way you might choose to do so for a class, but it is not permitted to define a destructor.

  


Professional C#. 2nd Edition
Performance Consulting: A Practical Guide for HR and Learning Professionals
ISBN: 1576754359
EAN: 2147483647
Year: 2002
Pages: 244

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net