C# struct
s are closely related class
s. They have state and behavior. They can have the same kinds of members: constructors, methods, fields, properties, etc.
Fields and properties can be simple types, struct
s or reference types. struct
s observe the same rules about scope, read/write rules and access levels as do class
s.
enum Unit
{
Kg,
Lb
}
struct Weight
{
private double count;
private Unit unit;
public Weight(double count, Unit unit)
{
this.count = count;
this.unit = unit;
}
public override string ToString()
{
return count.ToString() + unit.ToString();
}
}
new Weight(77.5, Unit.Kg).ToString();
// => "77.6Kg"
One of the main things to remember is that when one struct is assigned to a variable or passed as a parameter the values are copied across so changes to the original variable will not affect the copied one and vice versa. In summary, struct
s are value types.
This article discusses the differences between struct
s and class
s. You will see from the article that struct
s tend to be lightweight and immutable although this guidance is not enforced (by default) by the compiler or runtime.
There are a couple of things that you will come up against (and about which the compiler will remind you):
struct
cannot be initialized inline.struct
cannot be inheritedstruct
always has a default constructor even if a non-default one is provided, and you cannot provide an explicit parameterless constructor.As a result of points 1 and 3 above there is no way for the developer of a struct
to prevent invalid instances from coming into existence.
You will see from the documentation that there is a close relationship between primitives and structs. See Int32/int
, for an example. A more conventional example of astruct
is the type TimeSpan
.
Instances of TimeSpan
behave much like numbers with comparison operators like >
and <
and arithmetic operators. You can implement these operators for your own struct
s when you need them.
One thing to note about TimeSpan
is that it implements a number of interfaces e.g. IComparable<TimeSpan>
. Although struct
s cannot be derived from other struct
s they can implement interfaces.
Equality testing for struct
s can often be much simpler than that for class
s as it simply compares fields for equality by default. There is no need to override object.Equals()
(or GetHashCode()
). Remember that if you are relying on Object.GetHashCode()
you must still ensure that the fields involved in generating the hash code (i.e. all the fields) must not change while a hashed collection is use. Effectively, this means that structs used in this way should be immutable.
In contrast to the method, Equals()
, there is no default implementation of the equality operators, ==
and !=
. If your struct
needs them then you will have to implement them.
On the other hand, this article describes how performance can be optimised by creating your own custom Equals()
and GetHashCode()
method as is often done with class
s. The difference in the case of this exercise was about 20% in a not very rigorous comparison but that may be on the low side because all the fields are of the same type - see below.
There are discussions on the web about speed improvements, where the Equals()
method is not overridden, if all fields are of the same type. The difference in this exercise of including disparate fields was about 60%. This is not mentioned in Microsoft's documentation so that makes it an un-documented implementation detail, and it should be exploited judiciously.
public bool Equals(Weight other)
{
return count.Equals(other.count) && unit.Equals(other.unit);
}
public override bool Equals(object obj)
{
return obj is Weight other && Equals(other);
}
public override int GetHashCode()
{
return HashCode.Combine(count, unit);
}