Structures in C#

I’ve never been a big fan of using structs instead of classes in C#. I always thought, “If JavaScript, Python, and Groovy don’t need them, why should I?” They seem to exist mainly, if not solely, for performance optimization, which I probably wouldn’t need to worry about most of the time.

However, the introduction of ValueTuples in C# 7, which make working with multiple return values a breeze, has made me reconsider structs. One key performance advantage is that structs don’t have the Object header present in all reference types (class instances). This header takes up 8 bytes on 32-bit systems and 16 bytes on 64-bit systems. This can be a significant saving when dealing with many instances of a class or struct, as explained in huge difference.. Additionally, I’ve read that assignments and modifications can be a bit tricky with structs. After some research and testing, here’s a short overview. Most of the essentials are covered in This entry in MSDN.

Structs are value types. When you create a struct, the variable assigned to it holds the struct’s data directly. Assigning the struct to another variable creates a copy. Therefore, the original and new variables each hold independent copies, and changes to one don’t affect the other.

Value-type variables directly store their values, meaning the memory is allocated inline wherever the variable is declared. This means there’s no additional heap allocation or garbage collection overhead for value-type variables.

Heap vs Stack. Declaring a struct variable creates the struct on the stack: var myStruct = new MyStruct();. However, when a class has a struct as a field or property, the struct resides in the heap. Unlike a reference to a separate memory location, the field directly embeds the struct’s data.

Because structs are either on the stack or inlined, assignments are based on copying. Whether assigning a local struct variable, a struct field/property, or passing it as a parameter, the struct is copied. Here are some examples:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
    struct Address
    {
        public Address(string city, string street)
        {
            this.City = city;
            this.Street = street;
        }

        public string City;
        public string Street;

        public override string ToString()
        {
            return this.City + ", " + this.Street;
        }
    }

    class Person
    {
        public string Name {get;set;}
        public Address Location {get;set;}

        public Person(string name, Address location)
        {
            this.Name = Name;
            this.Location = location;
        }

        public override string ToString()
        {
            return this.Name + " - " + this.Location.ToString();
        }
    }

 
    //main
            var address1 = new Address("Marseille", "Port Vieux");
            //a copy of address1 is done and assigned to address2
            var address2 = address1;

            address1.Street = "Rue de la Republique";

            Console.WriteLine(address1.ToString()); //Rue de la Republique
            Console.WriteLine(address2.ToString()); //Port Vieux

            Console.WriteLine("----------------");
            
            //when we pass the struct as parameters its also a copy what gets passed
            var p1 = new Person("Francois", address1);

            Console.WriteLine(p1.ToString());
            //Republique

            address1.Street = "Rue du Temps";

            Console.WriteLine(p1.ToString());
            //Republique

            //with this assignment we are doing another copy
            p1.Location = address1;
            Console.WriteLine(p1.ToString());

            address1.Street = "Rue de la Liberte";
            Console.WriteLine(p1.ToString());
            //Temps

Modifying a struct through a field versus a property presents a crucial difference. With a struct field, direct modification is allowed: instance.structField.field = value;.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
struct Address
    {
        public Address(string city, string street)
        {
            this.City = city;
            this.Street = street;
        }

        public string City;
        public string Street;

        public override string ToString()
        {
            return this.City + ", " + this.Street;
        }
    }

   class Container
    {
  public Address AddressProp {get;set;}
        public Address AddressField;
        
        public override string ToString()
        {
            return "Prop: " + this.AddressProp.ToString() + " - Field: " + this.AddressField.ToString();
        }
    }
1
2
3
4
5
6
7
            var container = new Container();
            container.AddressField = new Address("Lyon", "Rue Victor Hugo");

            Console.WriteLine(container.ToString());
            //here I'm modifying the struct contents, the inlined values
            container.AddressField.Street = "Rue du Rhone";
            Console.WriteLine(container.ToString()); //Rhone

However, doing the same with a struct property (instance.structProperty.field = value;) results in a compilation error.

1
2
3
4
  container.AddressProp = new Address("Paris", "Boulevard Voltaire");
  //this line does not compile!!!
  container.AddressProp.Street = "Rue de Belleville";
 //Error: Cannot modify the return value of 'Container.AddressProp' because it is not a variable

This error makes sense because accessing the property returns a copy, not a reference, so the attempted modification is invalid.

Finally, assigning either a struct property or field to a variable creates a modifiable copy of the original struct.

Licensed under CC BY-NC-SA 4.0
Last updated on Nov 08, 2023 15:54 +0100