Site

Classes — C# Classes and Objects

Tutorial 6.0  •  C# / Learn

6.0 What This Teaches

This tutorial covers how C# defines and uses classes:

6.1 Class Syntax

A class groups related data (fields) and behavior (methods) under one type name. In top-level statement files you can define classes after the statements:
class Point
{
    public double X;
    public double Y;

    public double Distance() => Math.Sqrt(X * X + Y * Y);
}

Point p = new Point();
p.X = 3;
p.Y = 4;
Console.WriteLine(p.Distance());  // 5
Unlike C++, C# classes don't need a separate declaration header. Everything goes in one file. Members are private by default.

6.2 Fields and Properties

Prefer properties over public fields. An auto-property generates a hidden backing field automatically:
class Person
{
    public string Name { get; set; }    // read-write auto-property
    public int Age { get; set; }
    public string Id { get; init; }     // settable only during construction
}

Person alice = new Person { Name = "Alice", Age = 30, Id = "A001" };
alice.Name = "Alicia";   // OK - has set
// alice.Id = "A002";    // compile error - init-only
init accessors allow setting a property in an object initializer but prevent modification afterward - useful for immutable data.

6.3 Constructors

class Rectangle
{
    public double Width { get; }
    public double Height { get; }

    public Rectangle(double width, double height)
    {
        Width = width;
        Height = height;
    }

    // Overloaded constructor chaining with :this(...)
    public Rectangle(double side) : this(side, side) { }

    public double Area() => Width * Height;
    public double Perimeter() => 2 * (Width + Height);
}

var r1 = new Rectangle(4, 3);
var r2 = new Rectangle(5);   // square
Console.WriteLine(r1.Area());   // 12
Console.WriteLine(r2.Area());   // 25

6.4 Access Modifiers

Access modifiers control where a member is visible:
ModifierVisible to
publicAny code
privateThis class only (default for members)
protectedThis class and derived classes
internalThis assembly (project)
protected internalThis assembly or derived classes
class BankAccount
{
    private decimal _balance;             // private backing field

    public decimal Balance => _balance;  // read-only property

    public bool Deposit(decimal amount)
    {
        if (amount <= 0) return false;
        _balance += amount;
        return true;
    }
}

6.5 Static Members

Static members belong to the type, not any instance. Access them through the class name, not through an object reference:
class IdGenerator
{
    private static int _next = 1;

    public static int Next() => _next++;
    public static void Reset() => _next = 1;
}

int a = IdGenerator.Next();  // 1
int b = IdGenerator.Next();  // 2
IdGenerator.Reset();
int c = IdGenerator.Next();  // 1

6.6 Inheritance

Use : BaseClass to inherit. Mark base methods virtual and derived overrides with override. An abstract class cannot be instantiated directly:
abstract class Shape
{
    public string Color { get; init; } = "black";
    public abstract double Area();
    public virtual string Describe() =>
        $"{GetType().Name} area={Area():F2} color={Color}";
}

class Circle : Shape
{
    public double Radius { get; init; }
    public override double Area() => Math.PI * Radius * Radius;
}

class Rect : Shape
{
    public double Width { get; init; }
    public double Height { get; init; }
    public override double Area() => Width * Height;
}

6.7 Object Initializers

Object initializers set public properties without needing a matching constructor overload. They run after the constructor body:
class Config
{
    public string Host { get; set; } = "localhost";
    public int Port { get; set; } = 8080;
    public bool UseTls { get; set; }
}

// Overrides defaults selectively
var cfg = new Config { Host = "api.example.com", Port = 443, UseTls = true };
Console.WriteLine($"{cfg.Host}:{cfg.Port} tls={cfg.UseTls}");

6.8 Example - All Together

// Classes - shapes hierarchy printing area and description.

Shape[] shapes =
{
    new Circle { Radius = 3, Color = "red" },
    new Rect   { Width = 4, Height = 5, Color = "blue" },
    new Circle { Radius = 1 }
};

foreach (var s in shapes)
    Console.WriteLine(s.Describe());

abstract class Shape
{
    public string Color { get; init; } = "black";
    public abstract double Area();
    public virtual string Describe() =>
        $"{GetType().Name} area={Area():F2} color={Color}";
}

class Circle : Shape
{
    public double Radius { get; init; }
    public override double Area() => Math.PI * Radius * Radius;
}

class Rect : Shape
{
    public double Width { get; init; }
    public double Height { get; init; }
    public override double Area() => Width * Height;
}
Expected output:
Circle area=28.27 color=red
Rect area=20.00 color=blue
Circle area=3.14 color=black

6.9 Exercise

Exercise
  • Define a Student class with Name and Grade (int) properties and a LetterGrade property returning A/B/C/D/F.
  • Add a static List<Student> Roster and a static Register method that adds to it. Print all registered students.
  • Create a GraduateStudent derived class that adds a Thesis property and overrides ToString() to include it.

6.10 Common Mistakes

Forgetting virtual on the base method

Without virtual, a derived class can only hide the method with new, not override it. A base-typed reference will call the base version regardless of the actual runtime type. Always mark extensible methods virtual.

Null reference from uninitialized reference property

class Order
{
    public List<string> Items { get; set; }  // null until assigned
}
var o = new Order();
o.Items.Add("widget");  // NullReferenceException
Initialize reference properties in their declaration: public List<string> Items { get; set; } = new();

Public fields instead of properties

Public fields cannot add validation, cannot have independent get/set access, and don't work with data binding. Prefer auto-properties from the start, even when no logic is needed today.

6.11 Key Terms

TermMeaning
classReference type grouping fields and methods under one name
propertyMember with get/set accessors; preferred over public fields
auto-propertyCompiler-generated backing field: { get; set; }
initAccessor settable only during construction or object initializer
constructorSpecial method called with new to initialize an instance
access modifierKeyword (public, private, …) controlling member visibility
staticMember belonging to the type, not any instance
virtual / overrideMarks a base method as overridable; derived class overrides it
abstractClass or method with no implementation; derived class must provide one
object initializerBrace syntax setting properties immediately after new