6.0 What This Teaches
This tutorial covers how C# defines and uses classes:
- Class syntax: fields, properties, and constructors
- Access modifiers:
public, private, protected, internal
- Auto-properties and expression-bodied members
- Static members
- Inheritance and
virtual / override
- Abstract classes and object initializers
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:
| Modifier | Visible to |
public | Any code |
private | This class only (default for members) |
protected | This class and derived classes |
internal | This assembly (project) |
protected internal | This 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
| Term | Meaning |
| class | Reference type grouping fields and methods under one name |
| property | Member with get/set accessors; preferred over public fields |
| auto-property | Compiler-generated backing field: { get; set; } |
| init | Accessor settable only during construction or object initializer |
| constructor | Special method called with new to initialize an instance |
| access modifier | Keyword (public, private, …) controlling member visibility |
| static | Member belonging to the type, not any instance |
| virtual / override | Marks a base method as overridable; derived class overrides it |
| abstract | Class or method with no implementation; derived class must provide one |
| object initializer | Brace syntax setting properties immediately after new |