Using Structs in C#: How and Why?

c sharp generic listsUsing a struct in an object-oriented programming language like C# may at first seem unnecessary, even archaic. Classes are very similar and more flexible – they can inherit properties and methods from other classes, and are reference types that reduce the amount of memory used by a program. However, in most other aspects a struct is just as powerful and often less complicated to work with.

In discussing when and how you might choose to use them, a certain knowledge of C# is assumed. Programming for Complete Beginners in C# is an introduction to the topic, and the information presented here builds on that.

Beginners who prefer to learn VB.NET with Microsoft Visual Studio 2013 might also find the information on structs useful. Although the code syntax is different, VB.NET is also object-based and makes use of both classes and structs.

Defining Structures Using Structs

A struct is a data structure which defines a collection of objects and methods. Like an entry in the phone book of a cellphone, each instance of the struct contains the same pieces of information (such as the person’s name, telephone number, and email address).

There are three main benefits to using a struct over a class:

  • Structs are value types, and a copy of the data is created when you pass them into functions. This protects the original data in a way that is more complicated to achieve with a class.
  • As value types, the compiler places structs on the stack, not the heap. This can help improve the speed of a program.
  • Interoperability with non-managed code, such as the Win32 API for low-level Windows functions.

A simple struct declaration in C# looks like this:

public struct PhoneBook
{
public String name;
public String telephone;
public String email;
}

You should be familiar with everything here, except perhaps the use of the keyword struct. You can write this declaration inside a class, outside a class but within the same .cs file as a class, or in its own .cs file.

The properties of a struct cannot have initializers, unless those properties are marked as const or static. The following line is NOT valid.

public String name = "DEFAULT NAME";

Unlike a class, you do not always need to use the keyword new to use a struct. Structs can be declared like simple data types:

PhoneBook santa;

At this point, the object is ready to use:

PhoneBook santa;
santa.name = "Santa Claus";
santa.email = "santa@santaclaus.com";

Using Constructors

Creating and using a constructor is generally optional with structs. You can use a constructor when you want to perform some initialization of the object when it is created, or when a reference to the object will be needed. For example:

public struct PhoneBook
{
public String name;
public String telephone;
public String email;

public PhoneBook(String firstname, String lastname, String eAddress)
{
name = firstname + " " + lastname;
telephone = "";
email = eAddress;
}
}

There are two interesting rules to observe when creating constructors for structs:

  1. You can only create a constructor when you are passing arguments into it. Creating a constructor with no parameters will cause an error in the compilation of the program.
  2. You must assign a value to every property in the struct.

You can still create an object from the struct without using the constructor, but when you do choose to use it, the syntax is:

PhoneBook santa = new PhoneBook(“Santa”, “Claus”, “santa@santaclaus.com”);

Methods in Structs

Many people do not realize that you can create methods in structs, in the same way as you do in classes.

The following example extends the PhoneBook struct with a method that clears the values of the struct’s properties:

public struct PhoneBook
{
public String name;
public String telephone;
public String email;

public PhoneBook(String firstname, String lastname, String eAddress)
{
name = firstname + " " + lastname;
telephone = "";
email = eAddress;
}

public void Clear()
{
name = "";
telephone = "";
email = "";
}
}

This method can be called in the same way as you would call a method in a class:

PhoneBook santa = new PhoneBook("Santa", "Claus", "santa@santaclaus.com");
santa.Clear();

Arrays and Collections of Structs

You can certainly have arrays or collections of structs. However, doing this introduces a few complexities.

An array of structs is declared as for any other type of array. For example:

PhoneBook[] friends = new PhoneBook[1000];

However, this next code sample is NOT valid. Karl cannot be assigned as an object of the array.

PhoneBook Karl; Karl.name = “Karl”; Karl.email = “karl@example.com”; friends[0] = Karl;

Nor can it be added to a collection:

List<PhoneBook> myFriends = new List<PhoneBook>();
PhoneBook Karl;
Karl.name = "Karl";
myFriends.Add(Karl);

The compiler will report that Karl is unassigned.

When using a generic list or making array assignments in this way, you need to use a constructor for the struct, so that the compiler can correctly track references to the object. Since the PhoneBook example above already includes a constructor, using it as in the example below works:

List<PhoneBook> myFriends = new List<PhoneBook>();
PhoneBook Karl = new PhoneBook("Karl", "", "karl@example.com");
myFriends.Add(Karl);

When adding structs to arrays and collections, there is one other issue that you must be aware of. The following code works as expected:

PhoneBook[] friends = new PhoneBook[2];
friends[0].name = "Karl";
friends[1].name = "Sarah";

But when using a generic list or other types of object-based collection, the properties of a struct are immutable. They cannot be changed unless the struct is removed from the list, recreated, and then added again. The following example will return the compilation error “Cannot modify the return value of ‘…’ because it is not a variable.”

List<PhoneBook> myFriends = new List<PhoneBook>();
PhoneBook Karl = new PhoneBook("Karl", "", "karl@example.com");
myFriends.Add(Karl);
myFriends[0].name = "Karl";

XML Serialization

Serializing a struct to XML is often just as easy, if not easier, than serializing a class. Structs can be derived from the ISerializable interface, but this is not always needed. As when adding structs to a collection, you must use a constructor for your instances of the struct. The compiler needs a reference to pass into the serialization methods.

The following code sample is a basic example of how to convert an instance of the PhoneBook struct to XML.

PhoneBook Karl = new PhoneBook("Karl", "", "karl@example.com");
using (StringWriter tw = new StringWriter())
{
new XmlSerializer(typeof(PhoneBook)).Serialize(tw, Karl);
System.Diagnostics.Debug.WriteLine(tw.ToString());
}

The output of this code is:

 <?xml version="1.0" encoding="utf-16"?>
<PhoneBook xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<name>Karl </name>
<telephone />
<email>karl@example.com</email>
</PhoneBook>

If the XML code shown above means nothing to you, now would be a good time to learn XML programming as it has many uses in C# applications.

You can deserialize XML to a struct using similar code. With valid XML (such as the example above) in a string named xstring, the following sample creates a suitable stream and deserializes the XML to a PhoneBook struct.

using (TextReader tr = new StringReader(xstring))
{
XmlSerializer deserializer = new XmlSerializer(typeof(PhoneBook));
PhoneBook result = (PhoneBook)deserializer.Deserialize(tr);
System.Diagnostics.Debug.WriteLine("RESULT: " + result.name);
}

Structs and Classes

By now you have seen many of the similarities of structs and classes, and learned to work with structs in ways that you may have thought were only appropriate to classes.

To continue, try using them in your C# code, or investigate function and operator overloading. These two powerful techniques work just as well for both types of objects.