C# class types explained with examples

Published: Monday 16 March 2026

// ❌ This works… but don't add this
// 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.

Let's create a new 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.

If we add a non-static member to a static class:

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.

If you create two classes with the same name in the same namespace without the 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 everywhere
  • internal – Accessible within the assembly
  • private – Accessible within the containing type only
  • protected – Accessible within the containing type and derived classes
  • file – 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.