- Home
- .NET tutorials
- C# class types explained with examples
C# class types explained with examples
Published: Monday 16 March 2026
// in Program.cs
app.MapGet("/Product", () =>
new ProductDto(1, "Watch"));
We show you the correct way to organise Minimal API endpoints using separate endpoint classes → Learn more
We are going to look at the different C# class types and how they work.
Abstract
An abstract class is a base class that cannot be instantiated. It can contain abstract and non-abstract members and is designed for inheritance.
public abstract class Vehicle
{
public abstract int Wheels { get; }
public abstract void TurnOn();
public bool Started { get; protected set; }
}
Let's try and initialise this class:
var v = new Vehicle();
You'll get the following exception:
Cannot create an instance of the abstract type or interface 'Vehicle'
As stated, you cannot initialise an abstract class, but you can use it as a base class for inheritance.
Car class that inherits from Vehicle:
public class Car : Vehicle
{
}
We now get the following exceptions:
'Car' does not implement inherited abstract member 'Vehicle.TurnOn()'
'Car' does not implement inherited abstract member 'Vehicle.Wheels.get'
We need to override these abstract members in Car:
public class Car : Vehicle
{
public override int Wheels => 4;
public override void TurnOn()
{
// Turn key
Started = true;
}
}
Now when we initialise Car, the Wheels property is set to 4:
var car = new Car();
Console.WriteLine(car.Wheels); // Outputs 4
Sealed
A sealed class is one that cannot be inherited. Let's mark Vehicle as sealed:
public sealed class Vehicle
{
}
Now try to inherit from it:
public class Car : Vehicle
{
}
The Car class will throw this exception:
'Car': cannot derive from sealed type 'Vehicle'
This is because Vehicle can no longer be inherited.
Static
A static class is a class that cannot be instantiated or inherited. All members must be marked as static.
public static class SpeedHelper
{
// Missing 'static' keyword
public decimal ConvertToKph(decimal mph)
{
return mph * 1.6093m;
}
}
We get the following exceptions:
'ConvertToKph' cannot declare instance members in a static class
Member 'ConvertToKph' does not access instance data and can be marked as static
The solution is to mark the method as static:
public static class SpeedHelper
{
public static decimal ConvertToKph(decimal mph)
{
return mph * 1.6093m;
}
}
If we try to create a new instance of SpeedHelper:
var sh = new SpeedHelper();
We get this exception:
Cannot create an instance of the static class 'SpeedHelper'
To call ConvertToKph, we do the following:
Console.WriteLine(SpeedHelper.ConvertToKph(100)); // Outputs 160.93
Let's try to inherit from SpeedHelper:
public static class SpeedHelper : Car
{
}
public class Car
{
}
We get this exception:
Static class 'SpeedHelper' cannot derive from type 'Car'. Static classes must derive from object.
Partial
A partial class allows you to split a class across multiple files.
partial keyword:
public class Team
{
public string Name { get; set; }
}
public class Team
{
public int NoPlayers { get; set; }
}
You get the following exception:
The namespace '{Namespace}' already contains a definition for 'Team'
The solution is to mark both classes as partial:
public partial class Team
{
public string Name { get; set; }
}
public partial class Team
{
public int NoPlayers { get; set; }
}
You cannot have duplicate members with the same signature across partial classes. For example, two parameterless constructors:
public partial class Team
{
public Team() { }
public string Name { get; set; }
}
public partial class Team
{
public Team() { }
public int NoPlayers { get; set; }
}
This results in the following exception:
Type 'Team' already defines a member called 'Team' with the same parameter types
The constructor must exist in only one partial class:
public partial class Team
{
public Team() { }
public string Name { get; set; }
}
public partial class Team
{
public int NoPlayers { get; set; }
}
Unsafe
An unsafe class allows pointer-based code.
public unsafe class MemoryReader
{
public void Read(int* value)
{
Console.WriteLine(*value);
}
}
Unless you know what this is, you probably do not need to worry about it. If you do need it, you must enable unsafe code in your .csproj file by setting AllowUnsafeBlocks to true.
Record
A record is a reference type designed for data, not behaviour, and is immutable by default.
public record class Team(string Name, int NoOfPlayers);
If you try to modify one of the properties:
var team = new Team("Brighton", 11);
team.Name = "Manchester United";
You get this exception:
Init-only property or indexer 'Team.Name' can only be assigned in an object initializer, or on 'this' or 'base' in an instance constructor or an 'init' accessor.
Access modifiers
Here are the different access modifiers available in C#:
public– Accessible everywhereinternal– Accessible within the assemblyprivate– Accessible within the containing type onlyprotected– Accessible within the containing type and derived classesfile– The file modifier restricts a class to the current source file only
Use C# class types in a real API
If you want to use these C# class types in a real API, our Minimal APIs for complete beginners course is just for you. You'll use different C# class types and access modifiers with Minimal APIs, which is now Microsoft's recommended way of building Web APIs.
Watch the video
Watch this video where we go through the different C# class types and the exceptions you might encounter along the way.
Related pages