DEV Community

Cover image for Class, Record and Struct in C#
Mirza Leka
Mirza Leka

Posted on

Class, Record and Struct in C#

Class, Record, and Struct serve as blueprints for creating new objects.

Classes

Classes are the foundational building blocks of Object-Oriented Programming (OOP).

Blueprint and instance

          // blueprint
          public class Person
          {
            public int ID { get; set; }
            public string Name { get; set; }
          }

          // instance
          var p = new Person { ID = 1, Name = "Mirza" }
Enter fullscreen mode Exit fullscreen mode

Inspection

Printing the class instance in the console will just display it's name:

            var p1 = new Person { ID = 1, Name = "Mirza" };
            Console.WriteLine(p1); // Person
Enter fullscreen mode Exit fullscreen mode

To print the actual, we'd need print each property explicitly:

            Console.WriteLine(p1.ID); // 1
            Console.WriteLine(p1.Name); // "Mirza"
Enter fullscreen mode Exit fullscreen mode

Mutation

C# classes are mutable, meaning the originally set values can be altered:

            var p1 = new Person { ID = 1, Name = "Mirza" };
            Console.WriteLine(p1.Name); // "Mirza"

            p1.Name = "Armin";
            Console.WriteLine(p1.Name); // "Armin"
Enter fullscreen mode Exit fullscreen mode

That said, this can be tweaked by changing the accessor the class property.

            public class Person
            {
                public int ID { get; set; }
                public string Name { get; init; } // <--
            }
Enter fullscreen mode Exit fullscreen mode

This time around if we try to change the value of the Name property after initialization, the C# compiler will start to complain.

            var p1 = new Person { ID = 1, Name = "Mirza" };
            p1.Name = "Edis"; // ❌
Enter fullscreen mode Exit fullscreen mode

The init accessor allows you to create properties that can only be assigned a value during object initialization.

Memory location

Classes are reference types and are allocated on the heap. If we create two distinct class objects and assign one to the other, both objects will point to the exact same reference in memory:

            var p1 = new Person { ID = 1, Name = "Mirza" };
            var p2 = new Person { ID = 2, Name = "Mirza" };

            p1 = p2;
            p1.Name = "Sead";

            Console.WriteLine(p1.Name); // Sead
            Console.WriteLine(p2.Name); // Sead
Enter fullscreen mode Exit fullscreen mode

If we change a value in one of the objects, it will be reflected in both.

Equality

Two class instances (objects) aren't equal even if they share the same values:

            var p1 = new Person { ID = 1, Name = "Mirza" };
            var p2 = new Person { ID = 1, Name = "Mirza" };

            Console.WriteLine(p1 == p2); // False
            Console.WriteLine(p1.Equals(p2)); // False
            Console.WriteLine(ReferenceEquals(p1, p2)); // False
Enter fullscreen mode Exit fullscreen mode

To compare the two, we'd need to check each property on each class and compare to the other:

            private static bool AreEqual(Person? x, Person? y)
            {
              // If both are the same memory instance (or both null), they are equal
              if (ReferenceEquals(x, y)) return true;

              // If only one is null, they are not equal
              if (x is null || y is null) return false;

              // If actual values are equal
              return x.ID == y.ID && x.Name == y.Name;
            }
Enter fullscreen mode Exit fullscreen mode

In this case, the values of these two instances would indeed be equal.

            Console.WriteLine(AreEqual(p1, p2)); // True
Enter fullscreen mode Exit fullscreen mode

Deconstruction

Classes support deconstruction through manual implementation of the Deconstruct method:

            public class Person
            {
                public int ID { get; set; }
                public string Name { get; set; }

                public void Deconstruct(out int id, out string name)
                {
                    id = ID;
                    name = Name;
                }
            }
Enter fullscreen mode Exit fullscreen mode

Usage:

            var p = new Person { ID = 1, Name = "Mirza" };

            var (id, name) = p;

            Console.WriteLine(id); // 1
            Console.WriteLine(name); // "Mirza"
Enter fullscreen mode Exit fullscreen mode

Structs

Structs are lightweight data structures designed for small, simple data values.

Blueprint and instance

            // blueprint
            public struct Person
            {
                public int ID { get; set; }
                public string Name { get; set; }
            }

            // instance
            var p = new Person { ID = 1, Name = "Mirza" };
Enter fullscreen mode Exit fullscreen mode

Inspection

Just like classes, structs cannot print all contents at once:

            Console.WriteLine(p); // Person
            Console.WriteLine(p.ID); // 1
            Console.WriteLine(p.Name); // Mirza
Enter fullscreen mode Exit fullscreen mode

Mutation

Structs also allow mutation.

            var p = new Person { ID = 1, Name = "Mirza" };
            Console.WriteLine(p.Name); // Mirza

            p.Name = "Armin";
            Console.WriteLine(p.Name); // Armin
Enter fullscreen mode Exit fullscreen mode

Unless we forbid it using the init accessor.

            public struct Person
            {
                public int ID { get; set; }
                public string Name { get; init; } // <--
            }
Enter fullscreen mode Exit fullscreen mode

Memory location

The structs are value types and are (usually) allocated on the stack.

            var p1 = new Person { ID = 1, Name = "Mirza" };
            var p2 = new Person { ID = 2, Name = "Mirza" };

            p1 = p2;
            p1.Name = "Sead";

            Console.WriteLine(p1.Name); // Sead
            Console.WriteLine(p2.Name); // Mirza
Enter fullscreen mode Exit fullscreen mode

If we change a value in one of the objects, it won't reflect in both of them.

Equality

Structs are compared by value.

            var p1 = new Person { ID = 1, Name = "Mirza" };
            var p2 = new Person { ID = 1, Name = "Mirza" };

            Console.WriteLine(p1.Equals(p2)); // True
Enter fullscreen mode Exit fullscreen mode

Deconstruction

Structs support deconstruction through manual implementation of the Deconstruct method:

            public struct Person
            {
                public int ID { get; set; }
                public string Name { get; set; }

                public readonly void Deconstruct(out int id, out string name)
                {
                    id = ID;
                    name = Name;
                }
            }
Enter fullscreen mode Exit fullscreen mode

Usage:

            var p = new Person { ID = 1, Name = "Mirza" };

            var (id, name) = p;

            Console.WriteLine(id); // 1
            Console.WriteLine(name); // "Mirza"
Enter fullscreen mode Exit fullscreen mode

Records

Records are a newer type optimized for data-centric modeling, immutability, and built-in value equality. It brings the best of classes and structs.

Blueprint and instance

            // blueprint
            public record Person (int ID, string Name);

            // instance
            var p = new Person(1, "Mirza");
Enter fullscreen mode Exit fullscreen mode

The shorter syntax is the equivalent of doing:

            // blueprint
            public record Person
            {
                public int ID { get; init; }
                public string Name { get; init; }
            }

            // instance
            var p = new Person { ID = 1, Name = "Mirza" };
Enter fullscreen mode Exit fullscreen mode

Inspection

Printing the record instance will print the record and its contents:

            var p = new Person(1, "Mirza");
            Console.WriteLine(p); // Person { ID = 1, Name = Mirza }
Enter fullscreen mode Exit fullscreen mode

Mutation

Records are immutable by default:

            var p = new Person(1, "Mirza");
            p.Name = "Edis";  // ❌

            Console.WriteLine(p);
Enter fullscreen mode Exit fullscreen mode

Unless the record is declared using the class-like syntax and we change the default accessor from init to set:

            public record Person
            {
                public int ID { get; set; } // <-- defaults to init
                public string Name { get; set; } // <-- defaults to init
            }

            var p = new Person { ID = 1, Name = "Mirza" };
            p.Name = "Edis"; // βœ”οΈ

            Console.WriteLine(p.Name); // Edis
Enter fullscreen mode Exit fullscreen mode

Memory location

The records are reference types and are allocated on the heap.

            var p1 = new Person { ID = 1, Name = "Mirza" };
            var p2 = new Person { ID = 1, Name = "Mirza" };

            p1 = p2;
            p1.Name = "Sead";

            Console.WriteLine(p1.Name); // Sead
            Console.WriteLine(p2.Name); // Sead
Enter fullscreen mode Exit fullscreen mode

If we change a value in one of the objects, it will be reflected in both.

Equality

Just like Structs, Records are compared by value.

            var p1 = new Person(1, "Mirza");
            var p2 = new Person(1, "Mirza");

            Console.WriteLine(p1 == p2); // True
            Console.WriteLine(p1.Equals(p2)); // True
            Console.WriteLine(Object.ReferenceEquals(p1, p2)); // False
Enter fullscreen mode Exit fullscreen mode

Two objects share the same values, but do not point to the same reference in memory.

Deconstruction

Records support deconstruction out of the box.

            var p = new Person(1, "Mirza");

            var (id, name) = p;

            Console.WriteLine(id); // 1
            Console.WriteLine(name); // "Mirza"
Enter fullscreen mode Exit fullscreen mode

Bonus: Record Structs

Aside from Class-based Records, we can also create Records that behave as Structs.

  • Value type equality
  • Allocated (usually) on the stack
  • Mutable by default
  • All other benefits of Records
            // blueprint
            public record struct Person(int ID, string Name);
Enter fullscreen mode Exit fullscreen mode

Until next time πŸ‘‹

Top comments (0)