Structs are programmer-defined data types, very similar to classes. They have data members and function members. Although structs are similar to classes, there are a number of important differences. The most important ones are the following:
- Classes are reference types, and structs are value types.
- Structs are implicitly sealed, which means they cannot be derived from.
The syntax for declaring a struct is similar to that of declaring a class:
Keyword
↓
struct StructName
{
MemberDeclarations
}
For example, the following code declares a struct named Point
. It has two public fields, named X
and Y
. In Main
, three variables of struct type Point
are declared, and their values are assigned and printed out.
struct Point
{
public int X;
public int Y;
}
class Program
{
static void Main()
{
Point first, second, third;
first.X = 10; first.Y = 10;
second.X = 20; second.Y = 20;
third.X = first.X + second.X;
third.Y = first.Y + second.Y;
Console.WriteLine("first: {0}, {1}", first.X, first.Y);
Console.WriteLine("second: {0}, {1}", second.X, second.Y);
Console.WriteLine("third: {0}, {1}", third.X, third.Y);
}
}
This code produces the following output:
first: 10, 10
second: 20, 20
third: 30, 30
As with all value types, a variable of a struct type contains its own data. Consequently
- A variable of a struct type cannot be
null
.- Two structs variables cannot refer to the same object.
For example, the following code declares a class called CSimple
, a struct called Simple
, and a variable of each. Figure 10-1 shows how the two would be arranged in memory.
class CSimple
{
public int X;
public int Y;
}
struct Simple
{
public int X;
public int Y;
}
class Program
{
static void Main()
{
CSimple cs = new CSimple();
Simple ss = new Simple();
...
Assigning one struct to another copies the values from one to the other. This is quite different from copying from a class variable, where only the reference is copied.
Figure 10-2 shows the difference between the assignment of a class variable and a struct variable. Notice that after the class assignment, cs2
points at the same object in the heap as cs1
. But after the struct assignment, the values of ss2
’s members are copies of those in ss1
.
class CSimple
{ public int X; public int Y; }
struct Simple
{ public int X; public int Y; }
class Program
{
static void Main()
{
CSimple cs1 = new CSimple(), cs2 = null; // Class instances
Simple ss1 = new Simple(), ss2 = new Simple(); // Struct instances
cs1.X = ss1.X = 5; // Assign 5 to ss1.X and cs1.X.
cs1.Y = ss1.Y = 10; // Assign 10 to ss1.Y and cs1.Y.
cs2 = cs1; // Assign class instance.
ss2 = ss1; // Assign struct instance.
}
}
Structs can have instance and static constructors, but destructors are not allowed.
The language implicitly supplies a parameterless constructor for every struct. This constructor sets each of the struct’s members to the default value for that type. Value members are set to their default values. Reference members are set to null
.
The predefined parameterless constructor exists for every struct—and you cannot delete or redefine it. You can, however, create additional constructors, as long as they have parameters. Notice that this is different from classes. For classes, the compiler supplies an implicit parameterless constructor only if no other constructors are declared.
To invoke a constructor, including the implicit parameterless constructor, use the new
operator. Notice that the new
operator is used even though the memory is not allocated from the heap.
For example, the following code declares a simple struct with a constructor that takes two int
parameters. Main
creates two instances of the struct—one using the implicit parameterless constructor and the second with the declared two-parameter constructor.
struct Simple
{
public int X;
public int Y;
public Simple(int a, int b) // Constructor with parameters
{
X = a;
Y = b;
}
}
class Program
{
static void Main()
{ Call implicit constructor
↓
Simple s1 = new Simple();
Simple s2 = new Simple(5, 10);
↑
Call constructor
Console.WriteLine("{0},{1}", s1.X, s1.Y);
Console.WriteLine("{0},{1}", s2.X, s2.Y);
}
}
You can also create an instance of a struct without using the new
operator. If you do this, however, there are several restrictions, which are the following:
- You cannot use the value of a data member until you have explicitly set it.
- You cannot call any function member of the struct until all the data members have been assigned.
For example, the following code shows two instances of struct Simple
created without using the new
operator. When there is an attempt to access s1
without explicitly setting the data member values, the compiler produces an error message. There are no problems reading from s2
after assigning values to its members.
struct Simple
{
public int X;
public int Y;
}
class Program
{
static void Main()
{
No constructor calls
↓ ↓
Simple s1, s2;
Console.WriteLine("{0},{1}", s1.X, s1.Y); // Compiler error
↑ ↑
s2.X = 5; Not yet assigned
s2.Y = 10;
Console.WriteLine("{0},{1}", s2.X, s2.Y); // OK
}
}
As with classes, the static constructors of structs create and initialize the static data members and cannot reference instance members. Static constructors for structs follow the same rules as those for classes.
A static constructor is called before the first of either of the following two actions:
- A call to an explicitly declared constructor
- A reference to a static member of the struct
Field initializers are not allowed in struct declarations, as shown in the following code:
struct Simple
{ Not allowed
↓
public int x = 0; // Compile error
public int y = 10; // Compile error
}
↑
Not allowed
Structs are always implicitly sealed, and hence you cannot derive other structs from them.
Since structs do not support inheritance, the use of several of the class member modifiers with struct members would not make sense; thus, you cannot use them in their declarations. The modifiers that cannot be used with structs are the following:
protected
- internal
- abstract
virtual
Structs themselves are, under the covers, derived from System.ValueType
, which is derived from object
.
The two inheritance-associated keywords you can use with struct members are the new
and override
modifiers, when creating a member with the same name as a member of base class System.ValueType
, from which all structs are derived.
As with other value type data, if you want to use a struct instance as a reference type object, you must make a boxed copy. Boxing is the process of making a reference type copy of a value type variable. Boxing and unboxing are explained in detail in Chapter 16.
Structs can be used as return values and parameters.
- Return value: When a struct is a return value, a copy is created and returned from the function member.
- Value parameter: When a struct is used as a value parameter, a copy of the actual parameter struct is created. The copy is used in the execution of the method.
ref
andout
parameters: If you use a struct as aref
orout
parameter, a reference to the struct is passed into the method so that the data members can be changed.
Allocating structs requires less overhead than creating instances of a class, so using structs instead of classes can sometimes improve performance—but beware of the high cost of boxing and unboxing.
Finally, some last things you should know about structs are the following:
- The predefined simple types (
int
,short
,long
, and so on), although considered primitives in .NET and C#, are all actually implemented under the covers in .NET as structs.- You can declare
partial
structs in the same way aspartial
classes, as described in Chapter 6.
Structs, like classes, can implement interfaces, which will be covered in Chapter 15.
13.58.50.156