C# object
last modified October 17, 2026
In this article we show how to create and work with objects in C#.
An object is a basic building block of a C# program. An object is a combination of data and methods. The data and the methods are called members of an object.
Objects are created from types. The types are defined with the
class, struct, or record keywords. We
say that we create object instances of these types. All types in C#
directly or indirectly derive from the System.Object class, so
every value can be treated as an object.
Fields, properties, and methods of an object are accessed with the dot operator. Constructors are specialized methods for creating objects.
C# struct object
A structure is a value type. The type is defined with the struct
keyword. Structures are meant to represent lightweight objects like a point, a
rectangle, a colour and similar.
var p = new Point(2, 5);
Console.WriteLine(p);
public struct Point
{
private int x;
private int y;
public Point(int x, int y)
{
this.x = x;
this.y = y;
}
public override string ToString()
{
return $"Point x:{x}, y:{y}";
}
}
In the program, we create a lightweight object from a Point
structure. We also override the ToString method, which gives a
human-readable representation of the object.
$ dotnet run Point x:2, y:5
Primitive types are objects
C# primitive types are objects too. Under the hood, they are structures; for
instance, int is an alias for the System.Int32
structure.
float x = 12.3f;
int y = 34;
bool z = false;
Console.WriteLine(x.GetType());
Console.WriteLine(y.GetType());
Console.WriteLine(z.GetType());
Console.WriteLine(y.Equals(34));
Console.WriteLine(y.CompareTo(40));
Console.WriteLine(x.ToString("F2"));
Console.WriteLine(typeof(int).IsValueType);
We assign values of three primitive types to three variables.
Console.WriteLine(x.GetType()); Console.WriteLine(y.GetType()); Console.WriteLine(z.GetType());
The GetType method, inherited from System.Object,
returns the runtime type of the value.
Console.WriteLine(y.Equals(34));
Console.WriteLine(y.CompareTo(40));
Console.WriteLine(x.ToString("F2"));
We call methods on the values. The CompareTo method returns a
negative number, since 34 is less than 40.
$ dotnet run System.Single System.Int32 System.Boolean True -1 12.30 True
C# object type
The object keyword is an alias for the System.Object
class. A variable of the object type can hold a value of any type.
object[] items = [12, "falcon", 3.4, true, 'c'];
foreach (object item in items)
{
Console.WriteLine($"{item} is {item.GetType().Name}");
}
int n = 7;
object boxed = n;
int unboxed = (int) boxed;
Console.WriteLine($"{boxed} {unboxed}");
We store values of various types in an array of objects.
int n = 7; object boxed = n; int unboxed = (int) boxed;
When a value type is assigned to an object variable, it is
boxed: the value is copied into a new object on the heap. To get the
value back, we must unbox it with an explicit cast. Boxing has a
performance cost; this is why generic collections such as
List<int> are preferred over collections of objects.
$ dotnet run 12 is Int32 falcon is String 3.4 is Double True is Boolean c is Char 7 7
C# object's ToString method
Each object has a ToString method. It returns a human-readable
representation of an object. The default implementation returns the fully
qualified name of the type of the object. Note that when we call the
Console.WriteLine method with an object as a parameter, the
ToString method is called.
var b = new Being();
var o = new object();
Console.WriteLine(o.ToString());
Console.WriteLine(b.ToString());
Console.WriteLine(b);
class Being
{
public override string ToString()
{
return "This is Being class";
}
}
We have a Being class in which we override the default
implementation of the ToString method.
public override string ToString()
{
return "This is Being class";
}
Each class implicitly inherits from the object class. The
ToString method is a virtual method of this class. We use the
override keyword to replace its implementation.
var b = new Being(); var o = new object();
We create one custom defined object and one built-in object.
Console.WriteLine(o.ToString()); Console.WriteLine(b.ToString());
We call the ToString method on these two objects.
Console.WriteLine(b);
As we have specified earlier, passing an object to the
Console.WriteLine method calls its ToString method.
This time, we have called the method implicitly.
$ dotnet run System.Object This is Being class This is Being class
See the C# Object.ToString article for more details.
C# object fields
Fields are the data bundled in an instance of a type. They are also called instance variables or member fields. An instance field is a variable defined in a type, for which each object of the type has a separate copy.
var u1 = new User();
u1.name = "John Doe";
u1.occupation = "gardener";
var u2 = new User();
u2.name = "Roger Roe";
u2.occupation = "driver";
Console.WriteLine(u1);
Console.WriteLine(u2);
class User
{
public string? name;
public string? occupation;
public override string ToString()
{
return $"{name} is a {occupation}";
}
}
We have a User class with two member fields.
public string? name; public string? occupation;
We declare the name and occupation member fields. The public
keyword specifies that the member fields are accessible outside the class
block. The ? marks the fields as nullable, since they are not set
when the object is created.
var u1 = new User(); u1.name = "John Doe"; u1.occupation = "gardener";
We create an instance of the User class and set the name and
occupation fields. We use the dot operator to access the fields of objects.
Console.WriteLine(u1); Console.WriteLine(u2);
We print the two user objects to the console. Each object has its own values.
$ dotnet run John Doe is a gardener Roger Roe is a driver
Public fields are used here for simplicity. In real code, data is usually exposed through properties, as shown later.
C# object methods
Methods are functions defined inside the body of a type. They are used to perform operations with the data of our objects.
var c = new Circle();
c.SetRadius(5);
Console.WriteLine(c.Area());
class Circle
{
private int radius;
public void SetRadius(int radius)
{
this.radius = radius;
}
public double Area()
{
return radius * radius * Math.PI;
}
}
In the code example, we have a Circle class. We define two
methods.
private int radius;
We have one member field. It is the radius of the circle. The
private keyword is an access modifier. It tells that the field is
not accessible from outside the class. If we want to modify this field from the
outside, we must use the public SetRadius method.
public void SetRadius(int radius)
{
this.radius = radius;
}
This is the SetRadius method. The this keyword refers
to the current instance. The this.radius is the instance field,
while radius is the parameter, valid only inside the
SetRadius method. Since the parameter hides the field, the
this keyword is required here.
var c = new Circle(); c.SetRadius(5);
We create an instance of the Circle class and set its radius by
calling the SetRadius method on the object. We use the dot
operator to invoke the method.
public double Area()
{
return radius * radius * Math.PI;
}
The Area method returns the area of a circle. The
Math.PI is a built-in constant. There is no name conflict here,
so we can use the field without this.
$ dotnet run 78.53981633974483
C# object constructor
A constructor is a special method that is called when an object is created. Constructors are used to initialize fields.
var name = "Lenka";
var dob = new DateOnly(1990, 3, 5);
var u = new User(name, dob);
Console.WriteLine(u);
class User
{
private readonly string name;
private readonly DateOnly born;
public User(string name, DateOnly born)
{
this.name = name;
this.born = born;
}
public override string ToString() =>
$"{name} was born on {born:yyyy-MM-dd}";
}
We have a constructor for the User class.
var u = new User(name, dob);
We create the User object, passing its constructor two values.
This is the time when the constructor of the object is called.
public User(string name, DateOnly born)
{
this.name = name;
this.born = born;
}
Inside the constructor, we initialize the name and
born fields. The fields are readonly; they can be
assigned only in the constructor or in the declaration.
$ dotnet run Lenka was born on 1990-03-05
See the C# constructor article for more details.
C# object auto-implemented properties
Properties are members that provide access to the data of an object. C# has auto-implemented properties. With them, the compiler transparently provides the backing fields for us.
var u = new User();
u.Name = "John Doe";
u.Occupation = "gardener";
Console.WriteLine($"{u.Name} is a {u.Occupation}");
class User
{
public string? Name { get; set; }
public string? Occupation { get; set; }
}
We have a User class with two properties: Name
and Occupation.
var u = new User(); u.Name = "John Doe"; u.Occupation = "gardener";
From the outside, properties are used like fields.
public string? Name { get; set; }
public string? Occupation { get; set; }
Here we have two automatic properties. There is no implementation of the accessors and there are no member fields. The compiler generates them for us. Unlike fields, properties can later get validation logic or a different implementation without changing the code that uses them.
$ dotnet run John Doe is a gardener
C# reference and value semantics
Classes are reference types; structures are value types. A variable of a class type holds a reference to the object. A variable of a struct type holds the data itself. This matters when we assign one variable to another.
var p1 = new PointStruct { X = 1, Y = 2 };
var p2 = p1;
p2.X = 100;
Console.WriteLine($"struct: p1.X = {p1.X}, p2.X = {p2.X}");
var c1 = new PointClass { X = 1, Y = 2 };
var c2 = c1;
c2.X = 100;
Console.WriteLine($"class: c1.X = {c1.X}, c2.X = {c2.X}");
Console.WriteLine(ReferenceEquals(c1, c2));
struct PointStruct
{
public int X { get; set; }
public int Y { get; set; }
}
class PointClass
{
public int X { get; set; }
public int Y { get; set; }
}
We have a point structure and a point class with the same members.
var p1 = new PointStruct { X = 1, Y = 2 };
var p2 = p1;
p2.X = 100;
Assigning a struct copies its data. The p2 variable is an
independent copy; changing it does not affect p1.
var c1 = new PointClass { X = 1, Y = 2 };
var c2 = c1;
c2.X = 100;
Assigning a class instance copies only the reference. Both variables refer to
the same object, so the change is visible through both of them. The
ReferenceEquals method confirms that it is one object.
$ dotnet run struct: p1.X = 1, p2.X = 100 class: c1.X = 100, c2.X = 100 True
See the copy by value vs copy by reference article for more details.
C# object deconstruction
Deconstructing is unpacking types into single pieces; for instance, a tuple into its items or an object into its properties.
To deconstruct class instances, we have to implement the Deconstruct
method.
var u = new User("John", "Doe", "gardener");
var (fname, lname, occupation) = u;
Console.WriteLine($"{fname} {lname} is a {occupation}");
var (fn, ln) = u;
Console.WriteLine($"{fn} {ln}");
class User
{
public string FirstName { get; }
public string LastName { get; }
public string Occupation { get; }
public User(string fname, string lname, string occupation)
{
FirstName = fname;
LastName = lname;
Occupation = occupation;
}
public void Deconstruct(out string fname, out string lname)
{
fname = FirstName;
lname = LastName;
}
public void Deconstruct(out string fname, out string lname,
out string occupation)
{
fname = FirstName;
lname = LastName;
occupation = Occupation;
}
}
We can have multiple Deconstruct methods, as long as they have a
different number of parameters.
var u = new User("John", "Doe", "gardener");
var (fname, lname, occupation) = u;
Here, we deconstruct the User object into three variables.
var (fn, ln) = u;
Console.WriteLine($"{fn} {ln}");
Here, we deconstruct the user into two variables.
public void Deconstruct(out string fname, out string lname)
{
fname = FirstName;
lname = LastName;
}
The parameters of the Deconstruct method have the
out modifier.
$ dotnet run John Doe is a gardener John Doe
C# record object
A record is a type whose main purpose is to hold data. A record declared with
the record keyword is a reference type; record struct
declares a value type. Records allow us to create data objects quickly.
List<User> users =
[
new("John", "Doe", 1230),
new("Lucy", "Novak", 670),
new("Ben", "Walter", 2050),
new("Robin", "Brown", 2300),
];
users.ForEach(Console.WriteLine);
Console.WriteLine(users[0].FirstName);
Console.WriteLine(users[0].LastName);
Console.WriteLine(users[0].Salary);
var raised = users[0] with { Salary = 1500 };
Console.WriteLine(raised);
record User(string FirstName, string LastName, int Salary);
In the example, we have a list of User objects.
List<User> users =
[
new("John", "Doe", 1230),
new("Lucy", "Novak", 670),
new("Ben", "Walter", 2050),
new("Robin", "Brown", 2300),
];
A list of objects is created with a collection expression. Target-typed new expressions do not require the type name when the type is known; the compiler infers it from the element type of the list.
Console.WriteLine(users[0].FirstName); Console.WriteLine(users[0].LastName); Console.WriteLine(users[0].Salary);
We access the three properties of the record object with the dot operator.
var raised = users[0] with { Salary = 1500 };
The properties of a positional record are immutable. The with
expression creates a copy of the record with some properties changed.
record User(string FirstName, string LastName, int Salary);
The record is defined in one line. The compiler generates the properties, a
constructor, a Deconstruct method, value-based equality, and the
ToString method for us.
$ dotnet run
User { FirstName = John, LastName = Doe, Salary = 1230 }
User { FirstName = Lucy, LastName = Novak, Salary = 670 }
User { FirstName = Ben, LastName = Walter, Salary = 2050 }
User { FirstName = Robin, LastName = Brown, Salary = 2300 }
John
Doe
1230
User { FirstName = John, LastName = Doe, Salary = 1500 }
C# object equality
By default, two class instances are equal only if they are the same object; this is reference equality. Records use value equality: two records are equal if they have the same type and the same values.
var u1 = new User("John Doe", "gardener");
var u2 = new User("John Doe", "gardener");
Console.WriteLine(u1 == u2);
Console.WriteLine(u1.Equals(u2));
var r1 = new UserRecord("John Doe", "gardener");
var r2 = new UserRecord("John Doe", "gardener");
Console.WriteLine(r1 == r2);
Console.WriteLine(ReferenceEquals(r1, r2));
class User(string name, string occupation)
{
public string Name { get; } = name;
public string Occupation { get; } = occupation;
}
record UserRecord(string Name, string Occupation);
We compare two class instances and two records with the same data. The
User class uses a primary constructor to initialize its
properties.
$ dotnet run False False True False
The two User objects are not equal, although they hold the same
data. The two records are equal, even though they are two distinct objects.
Source
Objects - create instances of types
In this article we have worked with objects in C#.
Author
List all C# tutorials.