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):

C#
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++;
    }
}
Output
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

C#
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++;
}
Output
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:

C#
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++;
}
Output
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):

C#
Counter? maybe = null;
maybe.Increment();

class Counter
{
    public int Value;
    public void Increment() => Value++;
}
Output
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:

C#
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}";
}
Output
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:

C#
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 { }
Output
(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:

C#
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 };
}
Output
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.

Try it yourself · Program.cs
Edit the code, copy it into Program.cs and run it with dotnet run Program.cs (.NET 10 or later). How to set up .NET
Output
4 x 2.5: area 10, perimeter 13, square? False
3 x 3: area 9, perimeter 12, square? True