Classes and Objects
C# is an object-oriented language: you model your program as objects that bundle data with the operations on that data. A class is the blueprint — it says what data each object holds and what it can do — and each object created from it is an instance with its own copy of the data.
Defining a class
A class declares fields (the data) and methods (the behaviour). In a program with top-level statements, classes go after the statements, or in their own files (lesson 59):
var myDog = new Dog(); myDog.Name = "Biscuit"; myDog.Age = 3; myDog.Bark(); myDog.Birthday(); Console.WriteLine($"{myDog.Name} is now {myDog.Age}"); class Dog { public string Name = ""; public int Age; public void Bark() { Console.WriteLine($"{Name} says woof!"); } public void Birthday() { Age++; } }
Biscuit says woof! Biscuit is now 4
new Dog() creates an object. The dot accesses its members: myDog.Name reads or writes a field, myDog.Bark() calls a method. Inside the class, methods use the fields directly — Name means "this dog's name". public allows code outside the class to use a member (lesson 35).
Public fields like these are only for this first example. Real classes keep fields private and expose properties instead — lesson 33.
Each object has its own data
var a = new Counter(); var b = new Counter(); a.Increment(); a.Increment(); b.Increment(); Console.WriteLine($"a = {a.Value}, b = {b.Value}"); class Counter { public int Value; public void Increment() => Value++; }
a = 2, b = 1
The class is written once; every new creates a separate object with its own Value.
Objects are reference types
A variable of a class type holds a reference to the object. Assigning it to another variable gives two names for the same object:
var original = new Counter(); var sameOne = original; sameOne.Increment(); sameOne.Increment(); Console.WriteLine(original.Value); Console.WriteLine(ReferenceEquals(original, sameOne)); class Counter { public int Value; public void Increment() => Value++; }
2 True
null
A reference that doesn't point to any object is null. Calling a member on it throws a NullReferenceException — the most common crash in C# programs. The compiler warns about many of these in advance (lesson 43):
Counter? maybe = null; maybe.Increment(); class Counter { public int Value; public void Increment() => Value++; }
Program.cs(2,1): warning CS8602: Dereference of a possibly null reference. Unhandled exception. System.NullReferenceException: Object reference not set to an instance of an object.
Object initialisers
Instead of setting fields one statement at a time after new, put the assignments in braces:
var book = new Book { Title = "Dune", Author = "Frank Herbert", Pages = 412 }; Console.WriteLine(book.Describe()); Book other = new() { Title = "Emma", Author = "Jane Austen" }; Console.WriteLine(other.Describe()); class Book { public string Title = ""; public string Author = ""; public int Pages; public string Describe() => Pages > 0 ? $"{Title} by {Author}, {Pages} pages" : $"{Title} by {Author}"; }
Dune by Frank Herbert, 412 pages Emma by Jane Austen
ToString
Every class inherits a ToString method from object. By default it returns the class name, which isn't very helpful. Override it to give objects a meaningful text form — Console.WriteLine and string interpolation use it automatically:
var p = new Point { X = 3, Y = -2 }; var q = new Plain(); Console.WriteLine(p); Console.WriteLine($"Point is {p}"); Console.WriteLine(q); class Point { public int X; public int Y; public override string ToString() => $"({X}, {Y})"; } class Plain { }
(3, -2) Point is (3, -2) Plain
override replaces an inherited method; lesson 37 explains how that works.
Methods that use other objects
Methods can take objects as parameters and return new ones:
var home = new Point { X = 0, Y = 0 }; var shop = new Point { X = 3, Y = 4 }; Console.WriteLine(home.DistanceTo(shop)); Point mid = home.MidpointWith(shop); Console.WriteLine($"Midpoint: ({mid.X}, {mid.Y})"); class Point { public double X; public double Y; public double DistanceTo(Point other) { double dx = other.X - X, dy = other.Y - Y; return Math.Sqrt(dx * dx + dy * dy); } public Point MidpointWith(Point other) => new Point { X = (X + other.X) / 2, Y = (Y + other.Y) / 2 }; }
5 Midpoint: (1.5, 2)
Thinking in objects
When designing classes, look for the nouns in a problem — customer, order, invoice — as candidate classes, and the verbs — place, cancel, total — as their methods. A good class has one clear responsibility and keeps its data and the code that changes that data together.
Try it yourself
Complete a Rectangle class with Area, Perimeter and IsSquare methods, and a useful ToString.
4 x 2.5: area 10, perimeter 13, square? False 3 x 3: area 9, perimeter 12, square? True