Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

C# & .NET (C# 14, .NET 10 LTS)  Module 2 – Types, variables and operators

Value types and reference types

What C# copies when you assign or pass a struct, a class, an array or a string, why one change can show through two variables, and where the stack misleads.

  • Beginner
  • 15 minutes
  • Examples run with .NET 10.0.401 and Python 3.14.8
  • By MySmartCoPilot
Free core, Pro depth: 6 of 15 minutes free
Free: Assigning a struct copies its data; assigning a class copies a reference · A method gets a copy too: of the value, or of the reference · Which types are value types, and which are reference types. With a pass: 3 in-depth parts and 3 interview answers, about 9 minutes.

What you will learn

  • Predict what assignment and argument passing copy for structs, classes, arrays and strings
  • Detect accidental sharing of a mutable object, and remove it with a deep copy
  • Explain why the kind of a type, not the stack or the heap, decides how its values are copied
  • Compare structs, classes, records and strings by equality, default value and null

Before you start

What you’ll learn · Before you start

What you will learn

  • Predict what assignment and argument passing copy for structs, classes, arrays and strings
  • Detect accidental sharing of a mutable object, and remove it with a deep copy
  • Explain why the kind of a type, not the stack or the heap, decides how its values are copied
  • Compare structs, classes, records and strings by equality, default value and null

Before you start

On this page

b = a copies a Point struct, so setting b.X to 9 leaves a.X at 1. Declare Point as a class instead, and the same two lines change a.X to 9 as well, because a and b now name one object. Every C# type is one of two kinds, value type: A type whose variables hold the value itself, such as int, a struct or an enum. Assigning or passing such a value copies it, so a change to the copy never reaches the original. and reference type: A type whose variables hold a reference to an object, such as a class, string or array. Assigning or passing such a variable copies the reference, so both variables lead to the same object., and the kind decides what an assignment, an argument or a list entry copies.

You will predict what each assignment and call copies, catch an object shared by accident, and tell value types from reference types by what they copy, not by where they live.

Assigning a struct copies its data; assigning a class copies a reference

A variable of a value type holds its data, and assignment copies that data. A variable of a reference type holds a reference to an object, and assignment copies the reference, so both variables lead to the same object. This program runs the same three lines on a struct and on a class:

copy.cs: the same three lines on a struct and on a class C# · copy/copy.cs
using System.Globalization;

CultureInfo.CurrentCulture = CultureInfo.InvariantCulture;

// The same two fields, once in a struct and once in a class.
PointStruct a = new() { X = 1, Y = 2 };
PointStruct b = a;
b.X = 9;
Console.WriteLine($"struct: a = {a}, b = {b}");

PointClass c = new() { X = 1, Y = 2 };
PointClass d = c;
d.X = 9;
Console.WriteLine($"class:  c = {c}, d = {d}");
Console.WriteLine($"c and d are one object: {ReferenceEquals(c, d)}");

struct PointStruct
{
    public int X;
    public int Y;

    public override readonly string ToString() => $"({X}, {Y})";
}

class PointClass
{
    public int X;
    public int Y;

    public override string ToString() => $"({X}, {Y})";
}

Output

struct: a = (1, 2), b = (9, 2)
class:  c = (9, 2), d = (9, 2)
c and d are one object: True

Recorded with .NET SDK 10.0.401 (runtime 10.0.12) on macOS 26 arm64. To run it yourself: dotnet run copy.cs

b = a copied both fields of the struct, so setting b.X to 9 changed only b. d = c copied a reference, so d.X = 9 changed the one object that c names too, and ReferenceEquals(c, d) confirmed that there is only one. Step through what each line did:

Interactive · step by step What b = a and d = c copy Step through copy.cs line by line. The left column is the variables; an object of a class sits on its own, to the right.
What b = a and d = c copy, step 7 of 7. Showing a: PointStruct, b: PointStruct (X 9, Y 2), c: PointClass, d: PointClass, The PointClass object (X 9, Y 2).copies X and Ycopies the referencerefers torefers toa: PointStructX 1, Y 2b: PointStructX 9, Y 2c: PointClassa referenced: PointClassa referenceThe PointClass objectX 9, Y 2

The drawing shows the whole scene; its 7 steps are listed below.

Every step as text
  1. a holds its data. PointStruct a = new() { X = 1, Y = 2 } puts the numbers 1 and 2 in a itself. A variable of a value type contains its value.
  2. b = a. b = a copies both fields into b. There are now two points, each with its own X and Y.
  3. b.X = 9. Setting b.X changes b alone. a still holds X 1, as copy.cs printed: a = (1, 2), b = (9, 2).
  4. c refers to an object. new PointClass { X = 1, Y = 2 } creates an object of its own, and c holds a reference to it, not the numbers.
  5. d = c. d = c copies the reference, not the object. d now leads to the same object as c: there is still one point.
  6. d.X = 9. d.X = 9 changes the object, and c sees the change through its own reference: copy.cs printed c = (9, 2), d = (9, 2).
  7. One object, two values. ReferenceEquals(c, d) printed True: two variables, one object. The struct gave two separate values from the first assignment on.

Microsoft’s reference states the same rule: a variable of a value type contains an instance of the type, while a variable of a reference type contains a reference to one.

Key idea

Assignment always copies what a variable holds: the data itself for value types, a reference for reference types. That is why two variables of a class type can change one object.

Checkpoint

Check: What assignment copies

  1. Question 1 of 2 PointStruct a = new() { X = 1, Y = 2 }; PointStruct b = a; b.X = 9; what is a.X now?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: 1

    A variable of a value type holds its data, and assignment copies the data. After b = a, changing b leaves a as it was.

  2. Question 2 of 2 PointClass c = new() { X = 1 }; PointClass d = c; d = new PointClass { X = 5 }; what is c.X now?

    Choose one answer.

    A hint

    What did the last line change: the object, or the variable d?

    Show the answer: Question 2 of 2

    Answer: 1

    d = c made two names for one object, but d = new PointClass { … } only made d name a different object. Changing an object shows through every name for it; changing a variable does not.

A method gets a copy too: of the value, or of the reference

Passing an argument is an assignment to the parameter, so the same rule applies. By default C# passes every argument by value: the method gets a copy of a struct, or a copy of a reference. This program calls the same Shift code with a struct and with a class, then replaces a parameter, changes an array and changes a string:

passing.cs: five calls, and what each left behind in the caller C# · passing/passing.cs
using System.Globalization;

CultureInfo.CurrentCulture = CultureInfo.InvariantCulture;

PointStruct s = new() { X = 1, Y = 2 };
Moves.Shift(s);
Console.WriteLine($"after Shift(struct):    s = {s}");

PointClass c = new() { X = 1, Y = 2 };
Moves.Shift(c);
Console.WriteLine($"after Shift(class):     c = {c}");

Moves.Replace(c);
Console.WriteLine($"after Replace(class):   c = {c}");

int[] marks = [40, 55, 70];
Moves.AddGrace(marks);
Console.WriteLine($"after AddGrace(int[]):  marks = {string.Join(", ", marks)}");

string city = "pune";
Moves.Shout(city);
Console.WriteLine($"after Shout(string):    city = {city}");

static class Moves
{
    // The same code twice: it changes the method's own copy of a struct, or the one object a class reference names.
    public static void Shift(PointStruct p) => p.X += 10;

    public static void Shift(PointClass p) => p.X += 10;

    // Assigning to the parameter changes only the method's own variable.
    public static void Replace(PointClass p) => p = new PointClass { X = 100, Y = 100 };

    public static void AddGrace(int[] values)
    {
        for (int i = 0; i < values.Length; i++)
        {
            values[i] += 5;
        }
    }

    public static void Shout(string text) => text = text.ToUpperInvariant();
}

struct PointStruct
{
    public int X;
    public int Y;

    public override readonly string ToString() => $"({X}, {Y})";
}

class PointClass
{
    public int X;
    public int Y;

    public override string ToString() => $"({X}, {Y})";
}

Output

after Shift(struct):    s = (1, 2)
after Shift(class):     c = (11, 2)
after Replace(class):   c = (11, 2)
after AddGrace(int[]):  marks = 45, 60, 75
after Shout(string):    city = pune

Recorded with .NET SDK 10.0.401 (runtime 10.0.12) on macOS 26 arm64. To run it yourself: dotnet run passing.cs

Shift(s) changed the method’s own copy, so s stayed (1, 2), while Shift(c) followed its copy of the reference to the caller’s object, which became (11, 2). Replace(c) gave its parameter a new object, which changed only the method’s variable. An array is a reference type, so AddGrace changed the caller’s marks, and Shout left city alone, because ToUpperInvariant returns a new string.

Passed to a method The method gets Does a change show in the caller?
a struct a copy of its fields no
an object of a class a copy of the reference a change to the object does; a new object does not
an array a copy of the reference a change to an element does
a string a copy of the reference no: every change makes a new string

The ref, out and in modifiers change this rule; the module on methods covers them.

Expecting a new object to reach the caller

p = new PointClass { X = 100 } inside a method replaces the method’s own reference and nothing else. To hand the caller a new object, return it: c = MakePoint();.

Checkpoint

Check: Arguments passed by value

  1. Question 1 of 2 void Reset(PointClass p) { p = new PointClass(); } What does Reset(c) do to the caller's c?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: Nothing: c still refers to the same object, with the same values

    C# passes arguments by value: for a class, the method gets a copy of the reference. It can change the object through it, but not make the caller's variable name another object.

  2. Question 2 of 2 AddGrace(int[] values) adds 5 to every element. After AddGrace(marks) with marks = [40, 55, 70], what is marks[0]?

    Type a number.

    Show the answer: Question 2 of 2

    Answer: 45

    An array is a reference type, so the method got a copy of the reference to the caller's array and changed its elements: 40 became 45, as passing.cs printed.

Which types are value types, and which are reference types

Type.IsValueType says which kind a type is:

kinds.cs: eighteen types and their kind, then strings C# · kinds/kinds.cs
using System.Globalization;

CultureInfo.CurrentCulture = CultureInfo.InvariantCulture;

// Type.IsValueType says which of the two kinds a type is.
(string Name, Type Type)[] types =
[
    ("int", typeof(int)),
    ("double", typeof(double)),
    ("decimal", typeof(decimal)),
    ("bool", typeof(bool)),
    ("char", typeof(char)),
    ("DateTime", typeof(DateTime)),
    ("DayOfWeek, an enum", typeof(DayOfWeek)),
    ("(int, string), a tuple", typeof((int, string))),
    ("int?", typeof(int?)),
    ("PointStruct", typeof(PointStruct)),
    ("string", typeof(string)),
    ("int[]", typeof(int[])),
    ("object", typeof(object)),
    ("List<int>", typeof(List<int>)),
    ("Action, a delegate", typeof(Action)),
    ("PointClass", typeof(PointClass)),
    ("Booking, a record", typeof(Booking)),
    ("Fare, a record struct", typeof(Fare)),
];
foreach ((string name, Type type) in types)
{
    Console.WriteLine($"{name,-24} {(type.IsValueType ? "value type" : "reference type")}");
}

// string is a reference type that behaves like a value: no change shows through another variable.
Console.WriteLine();
string city = "Pune";
string copy = city;
copy += " Junction";
Console.WriteLine($"city = {city}, copy = {copy}");
string built = new(['P', 'u', 'n', 'e']);
Console.WriteLine($"built == city: {built == city}, one object: {ReferenceEquals(built, city)}");

struct PointStruct
{
    public int X;
    public int Y;
}

class PointClass
{
    public int X;
    public int Y;
}

record Booking(string Pnr);

record struct Fare(decimal Amount);

Output

int                      value type
double                   value type
decimal                  value type
bool                     value type
char                     value type
DateTime                 value type
DayOfWeek, an enum       value type
(int, string), a tuple   value type
int?                     value type
PointStruct              value type
string                   reference type
int[]                    reference type
object                   reference type
List<int>                reference type
Action, a delegate       reference type
PointClass               reference type
Booking, a record        reference type
Fare, a record struct    value type

city = Pune, copy = Pune Junction
built == city: True, one object: False

Recorded with .NET SDK 10.0.401 (runtime 10.0.12) on macOS 26 arm64. To run it yourself: dotnet run kinds.cs

The numeric types, bool, char, DateTime, enums, tuples such as (int, string), nullable value types such as int? and every struct are value types. string, arrays, object, collections such as List<int>, delegates, classes and records are reference types, while record struct declares a record that is a value type. An array is a reference type even when its elements are numbers. The kind matters most for the types you did not write: a DateTime passed to a method is a copy, while a List<int> passed the same way is the caller’s own list.

string is the reference type that behaves like a value. copy += " Junction" made a new string and left city unchanged, and built == city was true although ReferenceEquals showed two objects, because == on strings compares the text.

Checkpoint

Check: Which types are value types

  1. Question 1 of 2 Is int[] a value type or a reference type?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: A reference type

    Arrays, strings, classes, records, delegates and collections are reference types. The kind belongs to the type, not to what it holds.

  2. Question 2 of 2 string city = "Pune"; string copy = city; copy += " Junction"; what is city now?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: "Pune"

    string is a reference type that behaves like a value, because no operation changes a string: each one returns a new string, as kinds.cs showed.

Accidental sharing, reproduced and fixed with deep copies Pro: opens with a Pro, Premium or Ultimate pass

Most bugs with reference types come from two parts of a program changing one object while each believes it has its own. The runs below reproduce five ways this happens, then the three statements that C# refuses for structs because they would change or compare the wrong thing.

In this part:

  • Five ways to share an object without meaning to
  • Three struct statements that C# refuses, and what to write instead

4 runnable examples (C#, Python) · about 4 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Where values really live: the heap, the stack and the JIT, measured Pro: opens with a Pro, Premium or Ultimate pass

The stack and the heap are the most repeated explanation of value types. The run below counts the heap bytes of ten small pieces of code, and shows what really decides where a value lives.

In this part:

  • Ten pieces of code, their heap allocations counted

1 runnable example (C#) · about 4 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Equality, defaults and null for each kind, compared Pro: opens with a Pro, Premium or Ultimate pass

What == compares, what a variable holds before anything is assigned to it and whether it can be null all follow from the kind of its type. The run below compares two equal values of each kind, and counts them in a HashSet.

In this part:

  • What equality compares for each kind, and how a HashSet counts
  • How a record compares its members
  • Defaults and null

1 runnable example (C#) · about 5 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Practise: predict, then run

Change the struct to a class

Copy passing.cs to your computer and turn struct PointStruct into class PointStruct. Before you run it with dotnet run passing.cs, predict the first line of output; then run it and explain the change in one sentence.

Then answer the questions on copies, arguments and the two kinds of types, which begin where your answers so far are weakest:

Practice that adapts to you

Practise this lesson

9 questions about 9 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 9 What does b = a copy when a is a struct?

    Choose one answer.

    Show the answer: Question 1 of 9

    Answer: Every field of the struct

    Assignment copies what the variable holds. For a value type that is the whole value.

  2. Question 2 of 9 PointClass c = new() { X = 1 }; PointClass d = c; d.X = 9; what is c.X?

    Type a number.

    Show the answer: Question 2 of 9

    Answer: 9

    d = c copied the reference, so c and d name one object, and d.X = 9 changed it: copy.cs printed c = (9, 2).

  3. Question 3 of 9 A game keeps PointClass previous = current; and then does current.X += 5; every frame. Why does previous always show the new position, and what fixes it?

    Choose one answer.

    Show the answer: Question 3 of 9

    Answer: previous and current name one object; store a copy of the values, or make the point a struct

    Assigning an object of a class shares it. Keep a separate value: a struct, a record copied with a new value, or a new object made from the old one's fields.

  4. Question 4 of 9 By default, how does C# pass an argument of a class type?

    Choose one answer.

    Show the answer: Question 4 of 9

    Answer: By value: the method gets a copy of the reference

    Every argument is passed by value unless ref, out or in say otherwise. For a class, the value is a reference.

  5. Question 5 of 9 static void Shift(PointStruct p) { p.X += 10; } s.X is 1. What is s.X after Shift(s)?

    Type a number.

    Show the answer: Question 5 of 9

    Answer: 1

    The method got a copy of the struct and changed the copy, so s stayed (1, 2), as passing.cs printed.

  6. Question 6 of 9 A method must give its caller a brand-new list instead of the one passed in. Which design works without ref?

    Choose one answer.

    Show the answer: Question 6 of 9

    Answer: Return the new list, and let the caller assign it

    A method can change the object a reference leads to, but to replace the caller's reference it returns the new object, or takes the parameter with ref.

  7. Question 7 of 9 Which of these is a reference type?

    Choose one answer.

    Show the answer: Question 7 of 9

    Answer: string

    kinds.cs listed string, arrays, object, collections, delegates, classes and records as reference types.

  8. Question 8 of 9 Which of these are value types? Choose every one.

    Choose every answer that is right.

    Show the answer: Question 8 of 9

    Answer:

    • record struct Fare(decimal Amount)
    • DateTime
    • int?

    Structs, enums, tuples, nullable value types and record structs are value types; arrays, collections and record classes are reference types.

  9. Question 9 of 9 A method runs when = when.AddDays(1); on a DateTime parameter and list.Add(5); on a List<int> parameter. What does the caller see afterwards?

    Choose one answer.

    Show the answer: Question 9 of 9

    Answer: The 5 in its list, and its own date unchanged

    DateTime is a value type and List<int> a reference type, so the method changed only its copy of the date but the caller's list itself.

Interview questions

Fresher · What is the difference between a value type and a reference type in C#?

A variable of a value type, such as int, double, a struct or an enum, holds the value itself, so assignment and argument passing copy the value, and a change to the copy never reaches the original. A variable of a reference type, such as a class, string, an array or a delegate, holds a reference to an object, so assignment copies the reference and both variables lead to the same object: a change made through one shows through the other. string is a reference type that cannot change, so it behaves like a value. Where the memory lives is not the defining difference; what a copy copies is.

  1. Mid to senior

    A shop starts a reorder with new Order { Items = old.Items }. Customers report that changing a quantity in the reorder changes their past order too, and copying the list did not fix it. Explain the bug, fix it, and say how you would test for it.

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Mid to senior

    A teammate says that structs live on the stack and classes on the heap, so all the service's data transfer objects should become structs to save memory. What is right and what is wrong in this, and when is a struct the better choice?

    Model answer, Pro. First step and follow-ups

  3. Fresher to mid

    A customer is loaded twice from the database as two objects of a plain Customer class, and both end up in a HashSet<Customer>, although their Id is the same. Why does the set keep both, and what are your options to make it treat them as one customer?

    Model answer, Pro. First step and follow-ups

Key takeaways

  • A variable of a value type holds its data, and assignment copies the data, so b = a gives two separate structs.
  • A variable of a reference type holds a reference, and assignment copies the reference, so d = c gives two names for one object.
  • Arguments are passed by value: a method gets a copy of a struct or a copy of a reference, so it can change the caller’s object but cannot replace it.
  • Numbers, bool, char, enums, tuples and structs are value types, while classes, records, arrays, delegates and string are reference types, and string behaves like a value because it never changes.

Terms in this lesson

reference type
A type whose variables hold a reference to an object, such as a class, string or array. Assigning or passing such a variable copies the reference, so both variables lead to the same object.
value type
A type whose variables hold the value itself, such as int, a struct or an enum. Assigning or passing such a value copies it, so a change to the copy never reaches the original.

References

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.