Search⌘ K
AI Features

Classes

Explore the fundamentals of classes in C# including class definition, members like fields, properties, and methods, and how to create and use objects. Understand constructors, object initializers, and best practices to organize and maintain code effectively in modern C#.

C# is an object-oriented language, which means that C# programs are a set of interconnected objects.

A class is an object’s definition, and an object is an instance of a class. We can understand this through an analogy. Consider a blueprint for a car. The class is the blueprint, and the actual object is an instance of the Car class. Another example of a class is an abstract concept of a rectangle. Concrete examples that we draw are instances of this class.

Note: In older versions of C#, the default template explicitly included a Program class with a Main() method. In modern .NET, we use top-level statements, which allow us to write executable code directly in Program.cs without the boilerplate class and method wrapper. The compiler handles creating the entry point for us.

Creating classes

A class represents a user-defined type. A class is created using the class keyword:

class Car
{
}
A basic class definition

A class can be defined inside or outside a namespace, or inside another class. Typically, classes are placed in separate files.

Note: Following C# class structure best practices dictates creating a new file for each class and placing it inside a relevant namespace. This keeps your project organized and maintainable as it grows.

The following example demonstrates how to define a class using a file-scoped namespace, which is the standard in modern C#.

C# 14.0
namespace Classes;
// The class is inside a namespace
// The class is stored in a separate file
class Car
{
}
  • Line 1: Declares the namespace Classes for the entire file.

  • Line 5: Defines the Car class using the class keyword.

C# 14.0
using Classes;
Car familyCar; // Declared a variable of type Car
  • Line 1: Imports the Classes namespace so we can use the Car type. This is required because Car is inside the Classes namespace, but our top-level program is in the global namespace.

  • Line 3: Declares a variable familyCar of type Car. Note that this variable is not yet initialized.

Common mistake: If you forget to add using Classes;, the code will not compile. The compiler will show an error (CS0246) stating: The type or namespace name 'Car' could not be found. This happens because the Program.cs file (in the global namespace) cannot see inside the Classes namespace unless we explicitly import it.

The functionality of a class is provided by its members:

  • Fields are class variables that can be used to represent the state of the class.

  • Properties are wrappers around the fields that can control access and determine what kind of values can be assigned to those fields.

  • Methods perform some actions.

  • Events can notify class users that the state of the class has changed.

We will now add data members to the Car class.

C# 14.0
namespace Classes;
class Car
{
// Class fields
// We initialize strings to empty to ensure they are not null
public string model = "";
public int year;
public decimal price;
// Method
public string GetFullInformation()
{
return $"This {model} was manufactured in {year} and costs {price} dollars.";
}
}
  • Line 8: Declares a public field model and initializes it to an empty string to ensure safety.

  • Lines 9–10: Declares public fields year and price.

  • Lines 13–16: Defines a method GetFullInformation that returns a formatted string using the class's fields.

The declaration in Program.cs remains the same. We still only have a declaration; we have not created an object yet.

Here we have three fields: model, year, and price. A field is a variable defined at the class level. Because it’s a variable, we can choose to initialize it when we declare it:

public int year = 2021;

For fields and other members to be accessible outside of the class, we must denote that with the public modifier.

Because a class is essentially a new user-defined type, we can create variables of this type:

Car familyCar;

Currently, our familyCar variable doesn’t point to a concrete object.

Constructors

Apart from the methods that we’ve been creating, there are also special methods called constructors. A constructor is a method that’s called when we create an instance of a class. In other words, a constructor is used during object initialization.

The familyCar variable is not yet associated with an object. We create an instance of the Car class using the new keyword:

Car familyCar = new Car();

The new keyword allocates the required memory for the object and calls the constructor of the class. As a result, we have an area in computer memory to store the data for the Car object, and familyCar will point to that object.

We can instantiate Car without defining a constructor because C# generates a default one automatically. This default constructor doesn’t accept any parameters and does nothing other than create an empty instance of the class.

After creating an instance of the class, we can manipulate the members of the class through a variable that references an object of that class:

C# 14.0
using Classes;
using System;
// Create a new Car object
Car familyCar = new Car();
// Assign values to its fields
familyCar.year = 2021;
familyCar.model = "Lexus ES350";
familyCar.price = 60000;
// Print the output of its method
Console.WriteLine(familyCar.GetFullInformation());
  • Line 5: Creates a new instance of Car using the new keyword and the default constructor.

  • Lines 8–10: Assigns values directly to the object's public fields.

  • Line 13: Calls the GetFullInformation method on the object and prints the result to the console.

Creating custom constructors

If the default constructor doesn’t meet our requirements (for instance, because it doesn’t accept any parameters), we can create our own constructors.

public Car(string carModel)
{
model = carModel;
}
Defining a custom constructor

When we create a custom constructor, the default one isn’t created. Therefore, if we still need a default constructor, we must create it explicitly:

public Car()
{
}
Explicitly defining the default constructor

The following code playground demonstrates an example of a custom constructor. The comments explain what each part of the code does.

C# 14.0
namespace Classes;
class Car
{
public string model = "";
public int year;
public decimal price;
// Our custom constructor
public Car(string model)
{
// this keyword refers to the current instance of the class
this.model = model;
}
// Creating a custom constructor removes the default one
// If we want the default constructor to stay,
// we must create it explicitly
public Car()
{
}
// We can call another constructor by using this keyword
// Instead of setting the model one more time,
// we just called the constructor that sets it.
public Car(string model, int year) : this(model)
{
this.year = year;
}
public Car(string model, int year, decimal price) : this(model, year)
{
this.price = price;
}
public string GetFullInformation()
{
return $"This {model} was manufactured in {year} and costs {price} dollars.";
}
}
  • Line 10: Defines a constructor that accepts a model argument.

  • Line 19: Explicitly re-defines the parameterless (default) constructor so it remains available.

  • Line 26: Uses : this(model) to call the constructor defined on Line 11, avoiding code duplication.

  • Line 31: Uses : this(model, year) to call the constructor defined on Line 27.

C# 14.0
using Classes;
using System;
// Create a new Car object
// and assign the field values
// through the constructor
Car familyCar = new Car("Lexus ES350", 2021, 60000);
// Print the output of its method
Console.WriteLine(familyCar.GetFullInformation());
  • Line 7: Instantiates Car using the constructor that accepts three arguments (model, year, price).

Object initializers

Another way to assign values to object fields and properties is to use an initializer:

Car familyCar = new Car { model = "Lexus ES350", year = 2021, price = 60000 };

For this to work, a default empty constructor must be available. Otherwise, we have to call an available constructor and then use an initializer. This is redundant. The initializer runs after object creation, so it may overwrite values set by the constructor.